Preamble: The this keyword is one of the most complex mechanisms in JavaScript. It has a special keyword that is automatically defined in all function scopes. But even experienced JavaScript developers have a hard time figuring out what it’s pointing to.

— From JavaScript you Don’t Know, Volume 1

Let’s start with a few questions before we get started.
  • What is the definition of this?
  • There are several ways to bind. What are they? Who has a higher priority?
  • There are several ways to change the direction of this. What are they? How is the bottom layer implemented?
This definition of

This is bound at run time, not at write time. Its context depends on various conditions at function time. The binding of this has nothing to do with where the function is declared, but only how the function is called.

Here’s an example:

function foo () {
   var a = 2;
   this.bar();
}

function bar() {
    console.log(this.a)
}

foo();  // undefined

Copy the code

Resolution:

In fact, as you can see from the code above, When foo() is called, it is understood that the code resolves to window.foo() when this.bar() refers to the window, and console.log(this.a) printed in bar() is understood as Console.log (window.a) but the real var a = 2 is in foo() and does not exist in the current window object, so you can only type undefined.

Summary: This is actually a binding that happens when the function is called, and what it points to depends entirely on where the function is called.


First, understand the call location
Function baz() {// Current call stack: Function bar() {// The current call stack is baz -> bar // therefore, the current call position is global scope console.log('baz') bar()} function bar() {// The current call stack is baz -> bar // therefore, The current call location is in bar. console.log('bar') foo(); Function foo() {// The current call stack is baz -> bar -> foo // so the current call position is in bar. console.log('foo') } baz(); // <-- baz call locationCopy the code

Notice how we parse out the actual call location from the call stack, which determines the binding of this.


2. Binding rules
  • The default binding
  • Implicit binding
  • Explicitly bound
  • Explicit binding variants of hard binding
  • The new binding
The default binding
function foo () {
    console.log(this.a)
}

var a = 2;

foo();
Copy the code

Resolution:

function foo () {
    // console.log(this.a)
    console.log(window.a)
}

// var a = 2;
window.a = 2;

// foo();
window.foo();
Copy the code

** The first thing you should understand is that variables declared in the global scope will be mounted to the Window object, ** except, of course, for es6’s let, const, etc., which have temporary dead zones.

Conclusion:

In code,foo() is called directly with an undecorated function reference, which is the default binding.

Note:

If strict mode is used, global objects cannot be used for the default binding, so this is bound to undefined

function foo () {
    "use strict"

    console.log(this.a)
}

var a = 2;

foo(); // Cannot read property 'a' of undefined
Copy the code

Details: Calling foo() directly in strict mode does not affect the default binding.

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

var a = 2; ; (function(){
    "use strict"foo(); } ())Copy the code

Implicit binding

Example 1: Plain implicit binding

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

var obj = {
    a:2.foo:foo
}

obj.foo()
       
Copy the code

Resolution:

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

var obj = {
    a:2.// foo:foo,
    foo() {
        // When the function executes, this points to obj, console.log(obj.a).
        console.log(obj.a)
    }
}

obj.foo()

Copy the code

Example 2: The object attribute reference chain only affects the call location of the previous or last level.

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

var obj2 = {
    a:42.foo:foo
}

var obj1 = {
    a:2.obj2:obj2 
}

obj1.obj2.foo() / / 42
Copy the code

Resolution:

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

var obj2 = {

    a:42.// foo:foo // I'm at the last layer, I don't care who's in front of you, I'm pointing to you => obj2
    foo(){
        // When the function executes, this points to obj2, console.log(obj2.a).
        console.log(obj2.a)
    }
}

var obj1 = {
    a:2.obj2:obj2 
}

obj1.obj2.foo() / / 42
Copy the code

Example 3:

The implicit binding this is missing

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

var obj = {
    a:2.foo:foo
}

var bar = obj.foo // Function alias

var a = 'window'

bar()
Copy the code

Resolution:

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

/ / the first step
var obj = {
    a:2.// foo:foo
    foo(){
       /** * who is calling * 1, if obj.foo(); /** * who is calling * 1, if obj.foo(); A = 2; Because the current this points to a runtime binding. * 2, if it's function is an alias At this time In fact in the window. Xx, then point to is the global * * /
        console.log(this.a)
    }
}

/ / the second step
// if obj.foo() is executed directly; a = 2

/ / the third step
// var bar = obj.foo // Function alias
window.bar = function(){
    console.log(this.a) // this => window
}

var a = 'I'm the big picture'

bar()
Copy the code

Although bar is a reference to obj.foo, it actually refers to foo’s function itself, so the default binding is applied

Summary: When a function references a context object, the implicit binding rule binds this in the function call to that context object.


Explicitly bound

Plain explicit binding

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

var obj = {
    a:2
}

foo.call(obj)
Copy the code

Resolution:

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

var obj = {
    a:2.foo(){  // I'll borrow this method
        console.log(this.a)
    }
}

foo.call(obj)
Copy the code

So what does Call really do? Obj can borrow foo’s methods.

The realization of the call

Function.prototype.call_ = function (context, ... args) {
    var context = context || window;  // 1. What you don't have is global
    context.fn = this;  // 2, who is calling me, that I point to who

    varresult = context.fn(... args)//3. Execute the entire parameter
    delete context.fn;  // I will delete the previous information
    return result   
}
Copy the code

The realization of the apply

Function.prototype.apply_ = function (context, args) {
    var context = context || window;  // 1. What you don't have is global
    context.fn = this;  // 2, who is calling me, that I point to who

    varresult = context.fn(... args)//3. Execute the entire parameter
    delete context.fn;  // I will delete the previous information
    return result   
}
Copy the code

As you can see from the code, both implementations work the same way, but call passes plain arguments and apply passes arrays.


Hard binding

Show the binding variants -> bind

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

var obj = {
    a:2
}

var bar = function () {
    foo.call(obj)
}

bar();  / / 2

setTimeout(bar,1000);  / / 2

bar.call(window); / / 2
Copy the code

The principle of hard binding is that no matter how bar is called later, it will always manually call foo on OBj. Since hard binding is equivalent to bind binding, how does native BIND work?

The bind implementation:

Function.prototype.bind = function (context, ... args) {
    // 1, I must pass a function, < I am a method to mount the function prototype >
    if(typeof this! = ='function') {
        throw new Error('You have to pass a function.')}// 2, store the current pointer object
    var self = this;    

    // return a function
    var fbund = function () {
        // use the apply method to determine the bound prototype object, concatenate the outer function and return the arguments to the function
        return self.apply(this instanceof self ? this : context, args.concat([...arguments]))
    }

    // The current prototype stores and saves the prototype object
    if(this.prototype) {
        fbund.prototype = Object.create(this.prototype)
    }

    // Return a hard-bound function object; < Bar to me >
    return fbund
}
Copy the code

Application scenarios:

Wrapping function: accepts arguments and returns values

function foo (soms) {
   console.log(this.a,soms);
   return this.a + soms
}

var obj = {
   a:2
}

var bar = function () {
   return foo.apply(obj,arguments)}var b = bar(2)
console.log(b);
Copy the code

The new binding

To understand how the new binding works, first understand what happens when the new keyword is called.

  • Create a brand new object
  • This new object will be linked by implementation Prototype
  • This new object is bound to the function call’s this
  • If the function returns no other object, the function call in the new expression automatically returns the new object

Sample code:

function Foo (a) {
    this.a = a;
}

var bar = new Foo(2)

console.log(bar.a)
Copy the code

The implementation of the new

function _new (ctor, ... args) {
    // a function must be passed.
    if(typeofctor ! = ='function') {
        throw new Error('You have to pass a function.')}// create a new object
    let obj = new Object(a);// 3. Execute prototype link
    obj.__proto__ = Object.create(ctor.prototype);

    // 4. Pass the execution parameters
    let res = ctor.apply(obj,[...args]) 

    let isObject = typeof res === 'object'&& res ! = =null;
    let isFunction = typeof res === 'function';

    // check whether the constructor returns an object \ function. If it does, execute the object \ function
    return isObject || isFunction ? res : obj;
}
Copy the code

Arrow function
function foo () {
    return (a) = > {
        console.log(this.a)
    }
}

var obj1 = {
    a:2
};

var obj2 = {
    a:3
};

var bar = foo.call(obj1)

bar.call(obj2);
Copy the code

Parsing: The arrow function created inside foo() captures foo() ‘s this when called. Since foo()’ s this is bound to obj1, and bar(arrow function) ‘s this is bound to obj1, the binding of the arrow function cannot be modified.

Summary: One thing to know: The arrow function doesn’t have its own this at all, causing the inner this to point to the outer code’s this, which points to objects that are defined at definition and don’t point to their execution environment when called.


Judgment of binding priority

The first thing to be sure of is that the default binding has the lowest priority.

Which has higher priority, implicit or explicit binding?

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

var obj1 = {
   a:2.foo:foo
}

var obj2 = {
   a:3.foo:foo
}

obj1.foo();  / / 2
obj2.foo();  / / 3

obj1.foo.call(obj2); / / 3
obj2.foo.call(obj1); / / 2
Copy the code

Summary: Explicit binding has a higher priority.

Which has higher priority, implicit binding or new binding?

function foo (some) {
  this.a = some
}

var obj1 = {
   foo:foo
}

var obj2 = {}

obj1.foo(2);
console.log(obj1.a)    / / 2

var bar = new obj1.foo(4)
console.log(obj1.a)  / / 2
console.log(bar.a)   / / 4
Copy the code

Conclusion: New binding has a high priority.

Which has higher priority, explicit binding or new binding?

function foo (some) {
    this.a = some;
}

var obj1 = {};

var bar = foo.bind(obj1)
bar(2);

console.log(obj1.a) / / 2

var baz = new bar(3);
console.log(obj1.a) / / 2
console.log(baz.a) / / 3
Copy the code

Conclusion: New binding has a high priority.

Conclusion: New binding > Explicit binding > Implicit binding > Default binding.

conclusion

That’s all you need to know about this. Let’s do a few questions about this to reinforce what we have just seen.

4, About this interview question

Example code 1:

var a = 1
function foo () {
  var a = 2
  function inner () { 
    console.log(this.a) // ?
  }
  inner()
}

foo()

Copy the code

Example code 2:

var obj = {
  a: 1.foo: function (b) {
    b = b || this.a
    return function (c) {
      console.log(this.a + b + c)
    }
  }
}
var a = 2
var obj2 = { a: 3 }

obj.foo(a).call(obj2, 1)
obj.foo.call(obj2)(1)


Copy the code

Example code 3:

function foo1 () {
  console.log(this.a)
}
var a = 1
var obj = {
  a: 2
}

var foo2 = function () {
  foo1.call(obj)
}

foo2()
foo2.call(window)

Copy the code

Please refer to this interview question for more information