The this of each function is bound at the time of the call, depending entirely on where the function is called (that is, where the function is called). Regardless of the location of the declaration.

The position

Understand the call location: The call location is where the function is called (not where it is declared) at execution time.

To find where a function is called, the most important thing is to find the function’s call stack (that is, all functions called to get to the current execution position), and the function’s call position is the position before the current (top of the stack).

Take a chestnut

function baz() { 
 // The current call stack is baz
 // Therefore, the current call location is global scope, window under browser, global under node
 console.log( "baz" ); 
 bar(); // <-- bar call location
} 
function bar() {
 // The current call stack is baz -> bar
 // Therefore, the current call location is in baz
 console.log( "bar" ); 
 foo(); // <-- foo call location
} 
function foo() { 
 The current call stack is baz -> bar -> foo
 // Therefore, the current call location is in bar
 console.log( "foo" ); 
} 
baz(); // <-- baz call location
Copy the code

This binding rule

When executed by the JS engine, the this function is bound according to some rules.

The default binding

Default bindings apply to the most common type of function call: stand-alone function calls. Think of this rule as the default rule when no other rules can be applied.

function foo() {
 // By default, this refers to the global object, i.e. the top-level scope
 console.log( this.a ); 
}

var a = 2;
foo()/ / 2
Copy the code

How do you know that the default binding is applied? You can see how foo() is called by analyzing the call location. In code, foo() is called directly with an undecorated function reference, so only the default binding can be used and no other rules can be applied.

In strict mode, global objects cannot be used for default bindings, so this is bound to undefined, the same in browsers and nodes.

A subtle but important detail here is that while the binding rules for this depend entirely on the location of the call, the default binding is bound to the global object only if foo() is run in non-strict mode; Calling foo() in strict mode does not affect the default binding:

function foo() { 
 // Run in non-strict mode
 console.log( this.a ); 
} 
var a = 2; 
(function(){ 
 // Call in strict mode
 "use strict"; 
 foo(); / / 2}) ();Copy the code

The above code uses a mixture of strict and non-strict modes, so foo’s this is not affected by strict mode, but mixing strict mode is discouraged, and fortunately ES6 defaults to strict mode.

Implicit binding

When a reference to a function is held by an object (as a method on that object), then the function’s this is bound to that object. Typically this is triggered when an object is declared with a declared function as a property of that object.

function foo() { 
 console.log( this.a ); 
} 
var obj = { 
 a: 2.foo: foo 
}; 
obj.foo(); / / 2
Copy the code

When an obj object is declared, foo is treated as an attribute of obj, so its this is implicitly bound to obj because obj holds a reference to Foo.

In the chain of object property references, only the previous or last level counts in the call location.

Take a chestnut

function foo() { 
 console.log( this.a ); 
} 

// obj2.foo refers to the foo function
var obj2 = { 
 a: 42.foo: foo 
}; 

//obj1. Obj2 references obj1 objects
var obj1 = { 
 a: 2.obj2: obj2 
}; 

// But this in foo always points to the object that directly holds its reference, namely obj2
obj1.obj2.foo(); / / 42
Copy the code

A reference to a function is held by an object, a reference to that object is held by another object, a reference to that object is held by another object… , which is like a necklace, but no matter how deep the hierarchy, the function’s this always points to the object that directly holds its reference.

Implicit loss

One of the most common problems with this binding is that the implicitly bound function loses the binding object, that is, it applies the default binding, which binds this to the global object or undefined, depending on whether it is in strict mode or not.

function foo() { 
 console.log( this.a ); 
} 
var obj = { 
 a: 2.foo: foo 
}; 
var bar = obj.foo; // Function alias!
 
var a = "oops, global"; // A is a property of the global object
bar(); // "oops, global"
Copy the code

Although bar is a reference to obj.foo, it is actually a reference to the function foo itself, so bar() at this point is an undecorated function call, so the default binding is applied.

Another example occurs when a callback function is passed in

function foo() { 
 console.log( this.a ); 
} 
function doFoo(fn) { 
 // fn refers to foo
 fn(); // <-- call location! Obviously, this is the default binding
}
var obj = { 
 a: 2.foo: foo 
}; 
var a = "oops, global"; // A is a property of the global object
doFoo( obj.foo ); // "oops, global"
Copy the code

Parameter passing is actually an implicit assignment, so when we pass in the function we get an implicit assignment, so the result is the same as in the previous example.

What happens if you pass functions into the language’s built-in functions instead of your own declared functions? The result is the same, no difference:

function foo() { 
 console.log( this.a );
}
var obj = { 
 a: 2.foo: foo 
}; 
var a = "oops, global"; // A is a property of the global object
setTimeout( obj.foo, 100 ); // "oops, global"
Copy the code

Once again, this proves that the function this is bound at runtime, regardless of where it is declared.

In addition, there is another case in which this can behave in a surprising way: the function that called the callback might modify this. In some popular JavaScript libraries, event handlers often bind this to the callback function to the DOM element that triggers the event. Such as onClick, addEventListener, bind this to the DOM element.

Explicitly bound

A display binding binds this to a specified context using some built-in functions provided by JS.

More specifically, you can use the function’s call(..) And the apply (..) Methods. Strictly speaking, JavaScript’s hosting environment sometimes provides very special functions that do not have either method. But such functions are rare, and most of the functions provided by JavaScript and any functions you create yourself can use call(..) And the apply (..) Methods.

function foo() { 
 console.log( this.a ); 
} 
var obj = { 
 a:2 
}; 
// When executed, foo's this is obj
foo.call( obj ); / / 2
Copy the code

If you pass in a primitive value (String, Boolean, or numeric) as the binding object for this, the primitive value is converted to its object form (i.e., new String(..)). , new Boolean (..) Or the new Number (..) ). This is often referred to as “boxing”.

Explicit binding still does not solve the problem of lost bindings.

Hard binding

Hard binding is a variant of explicit binding.

function foo() { 
 console.log( this.a ); 
}
var obj = { 
 a:2 
}; 
var bar = function() { 
 foo.call( obj ); 
}; 
bar(); / / 2
setTimeout( bar, 100 ); / / 2
// A hard bound bar cannot change its this
bar.call( window ); / / 2
Copy the code

It makes sense to bind this function to this when it runs.

A typical application scenario for hard binding is to create a wrapper function that accepts parameters and returns values

function foo(something) { 
 console.log( this.a, something ); 
 return this.a + something; 
} 
var obj = { 
 a:2 
}; 
var bar = function() { 
 return foo.apply( obj, arguments ); 
}; 
var b = bar( 3 ); / / 2, 3
console.log( b ); / / 5
Copy the code

Another way to use it is to create a helper function that can be reused

function foo(something) { 
 console.log( this.a, something ); 
 return this.a + something; 
} 
// Simple helper binding function
function bind(fn, obj) { 
 return function() { 
 return fn.apply( obj, arguments ); 
 }; 
} 
var obj = { 
 a:2 
}; 
var bar = bind( foo, obj ); 
var b = bar( 3 ); / / 2, 3
console.log( b ); / / 5
Copy the code

Since hard binding is a very common pattern, ES5 provides built-in methods function.prototype.bind, bind(..) Returns a hard-coded new function that sets the argument you specify to the context of this and calls the original function.

function foo(something) { 
 console.log( this.a, something ); 
 return this.a + something; 
}
var obj = { 
 a:2 
}; 
var bar = foo.bind( obj ); 
var b = bar( 3 ); / / 2, 3
console.log( b ); / / 5
Copy the code

Optional call “context” in API

Many functions in third-party libraries, as well as many new built-in functions in the JavaScript language and host environments, provide an optional argument, often called a “context,” that functions in conjunction with bind(..). Again, make sure your callback uses the specified this. These functions are actually done by calling (..) Or apply (..) Explicit binding is implemented.

function foo(el) { 
 console.log( el, this.id ); 
} 
var obj = { 
 id: "awesome" 
}; 
/ / calls foo (..) Bind this to obj
[1.2.3].forEach( foo, obj ); 
// 1 awesome 2 awesome 3 awesome
Copy the code

The new binding

When new is used to call a function (which is also an object), or when a constructor call occurs, the following actions are automatically performed:

  1. Create (or construct) an entirely new object.
  2. The new object will be executed in the prototype chain[[Prototype]]The connection.
  3. The new object will be bound to the function callthis.
  4. If the function returns no other object, thennewA function call in an expression automatically returns this new object.
function foo(a) { 
 this.a = a; 
} 
var bar = new foo(2); 
console.log( bar.a ); / / 2
Copy the code

Use new to call foo(..) We construct a new object and bind it to foo(..). On this in the call.

The priority of the rule

In actual judgment, a scene may have multiple rules. Therefore, the decision should be made from the highest priority down.

It can be judged in the following order:

  1. Is the function called in new (new binding)? If so, this binds to the newly created object.

  2. Are functions invoked by call, apply (explicit binding), or hard binding? If so, this is bound to the specified object.

  3. Is the function called in a context object (implicit binding)? If so, this binds to that context object.

  4. If neither, use the default binding. If in strict mode, it is bound to undefined, otherwise it is bound to a global object.

Rules of exception

In some cases the behavior of this binding is unexpected, and when you think you should apply other binding rules, you might actually apply the default binding rules.

When using explicit binding methods such as bind(),apply(), null or undefined is passed as this to be bound to

2. The return value of the assignment expression

function foo() { 
 console.log( this.a ); 
}
var a = 2; 
var o = { a: 3.foo: foo }; 
var p = { a: 4 }; 
o.foo(); / / 3
(p.foo = o.foo)(); / / 2
Copy the code

The return value of the assignment expression p.foo = o.foo is a reference to the target function, so the call location is foo() instead of p.foo() or o.foo(). As we said before, default bindings will apply here.

3. Soft binding

Hard binding is a good way to solve the problem that implicit binding can inadvertently bind this to the top-level action object (undefined in strict mode), but reduces its flexibility. The result is to retain the flexibility to bind to the specified this without having it default to a global object. The solution is soft binding.

In general, there is a default value. If the binding object is specified, it is bound to the specified object, otherwise it is bound to the default object.

// Implements the same effect as hard binding, while retaining the ability to modify this with either implicit or explicit binding.
if (!Function.prototype.softBind) { 
 Function.prototype.softBind = function(obj) { 
 var fn = this; 
 // Capture all curried parameters
 var curried = [].slice.call( arguments.1 ); 
 var bound = function() { 
 return fn.apply( 
 (!this || this= = = (window || global))? obj :this,
 curried.concat.apply( curried, arguments)); }; bound.prototype =Object.create( fn.prototype ); 
 return bound; 
 }; 
}
Copy the code

conclusion

To determine the this binding of a running function, you need to find where the function was called directly.

Once found, the following four rules can be applied in order to determine the binding object for this.

  1. bynewCall? Bind to the newly created object.
  2. bycallorapply(orbind) call? Bind to the specified object.
  3. Called by the context object? Bind to that context object.
  4. Default: bound to in strict modeundefinedOtherwise it is bound to a global object.

Instead of following the four standard binding rules, the arrow function determines this based on the current lexical scope. Specifically, the arrow function inherits the this binding of the outer function call (whatever this is bound to). This has the same effect as if we created a variable to hold the current “this”.