What this?
When a function is invoked, an execution context is created. This includes information about where the function was called from(call-stack), how the function was invoked(call-site), what parameters were passed and what this reference was bound to.
this is actually a runtime binding that is made when a function is invoked, and what it references is determined entirely by the call-site where the function is called.
Why this?
- Reuse the function by providing different context objects.
- Simplify the interface of the function, implicitly passing along an object reference.
this
Call-Site
The location in code where a function is called(not where it is declared)
Binding
1. Default Binding: It occurs with a standalone function invocation{ fun(); }. This is bound to a undefined in strict mode, global object otherwise.
2. Implicit Binding: the call-site has a context object { obj. fun(); }; then this references to that context object. Only the last level of an object property reference chain matters to the call-site. { obj1.obj2.fun(); }
Implicitly Lost: when an implicitly bound function loses that binding, which usually means it falls back to the default binding.
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
var a = "oops, global"; // `a` also property on global object
setTimeout( obj.foo, 100 ); // "oops, global"function setTimeout(fn, delay) {
fn(); // <-- call-site!
}/* Solution */
var bar = foo.bind(obj);
setTimeout(bar, 100);
3. Explicit Binding: call(…) and apply(…) both take, as their first parameter, an object to use for the this, then invoke the function with that this specified.
Hard-binding greatly reduces the flexibility of a function, preventing manual this
override with either the implicit binding or even subsequent explicit binding attempts.
The most typical way to wrap a function with a hard binding creates a pass-thru of any arguments passed and any return value received:
// simple `bind` helper
function bind(fn, obj) {
return function() {
return fn.apply( obj, arguments );
};
}
API call “context”: the api offers an optional parameter.
function foo(el) {
console.log( el, this.id );
}
var obj = {
id: "awesome"
};
// use `obj` as `this` for `foo(..)` calls
[1, 2, 3].forEach( foo, obj ); // 1 awesome 2 awesome 3 awesome
Apply: fun.apply(thisArg, [argsArray])
Call: fun.call(thisArg, arg1, arg2, ...)
Bind: fun.bind(thisArg[, arg1[, arg2[, …]]]) // hard binding
function foo(a,b) {
console.log( "a:" + a + ", b:" + b );
}
// spreading out arrays of values as parameters to a function call
foo.apply( null, [2, 3] ); // a:2, b:3// arguments for the function
foo.call(null, 2, 3);// curry parameters(preset values)
var bar = foo.bind( null, 2 );
bar( 3 ); // a:2, b:3
4. new Binding
When a function is invoked with new
in front of it, otherwise known as a constructor call, the following things are done automatically:
- a brand new object is created (aka, constructed) out of thin air
- the newly constructed object is [[Prototype]]-linked
- the newly constructed object is set as the this binding for that function call(call-site)
- Unless the function returns its own alternate object, the new-invoked function call will automatically return the newly constructed object.
function Foo(a) {
this.a = a;
}/* constructed a new object and set that new object as the this for the call of foo(..) */ var bar = new Foo( 2 );
console.log( bar.a ); // 2
Determining this
Determining the this
binding for an executing function requires finding the direct call-site of that function. Once examined, four rules can be applied to the call-site, in this order of precedence:
- Called with
new
? Use the newly constructed object. - Called with
call
orapply
(orbind
)? Use the specified object. - Called with a context object owning the call? Use that context object.
- Default:
undefined
instrict mode
, global object otherwise.
Binding Exceptions
safer this
Be careful of accidental invoking of the default binding rule. In cases where you want to “safely” ignore a this
binding, a "DMZ" object like ø = Object.create(null)
is a good placeholder value that protects the global
object from unintended side-effects.
// our DMZ empty object
var ø = Object.create( null );
// spreading out array as parameters
foo.apply( ø, [2, 3] ); // a:2, b:3
Indirection
function foo() {
console.log( this.a );
}
var a = 2;
var o = { a: 3, foo: foo };
var p = { a: 4 };
o.foo(); // 3
/* the result value of the assignment expression p.foo = o.foo is a reference to just the underlying function object. */
(p.foo = o.foo)(); // 2
Softening Binding
It would be nice if there was a way to provide a different default for default binding (not global
or undefined
), while still leaving the function able to be manually this
bound via implicit binding or explicit binding techniques.
Lexical this
Problem: this is not included in the closure.
Solution:
- ES6 arrow function: arrow-functions adopt the
this
binding from the enclosing (function or global) scope.
function foo() {
// return an arrow function
return (a) => {
// `this` here is lexically adopted from `foo()`
console.log( this.a );
};
}
var obj1 = {
a: 2
};
var obj2 = {
a: 3
};/* The lexical binding of an arrow-function cannot be overridden (even with new!) */var bar = foo.call( obj1 );
bar.call( obj2 ); // 2, not 3!
2. use a variable self to store this (pre-ES6)
function foo() {
var self = this; // lexical capture of `this`
setTimeout( function(){
console.log( self.a );
}, 100 );
}
var obj = {
a: 2
};
foo.call( obj ); // 2
3. use bind(…);