邹明潮 Notes: YDKJS this

邹明潮
KevinZou
Published in
5 min readApr 28, 2017

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?

  1. Reuse the function by providing different context objects.
  2. 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:

  1. Called with new? Use the newly constructed object.
  2. Called with call or apply (or bind)? Use the specified object.
  3. Called with a context object owning the call? Use that context object.
  4. Default: undefined in strict 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 globalobject 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:

  1. 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(…);

--

--