Analysis of this binding mechanism in Javascript - detailed explanation

Why use this:

function identify() {
  console.log("Hello,I'm " + this.name);
}
let me = {
  name: "Kyle"
};
let you = {
  name: "Reader"
};
identify.call(me); // Hello,I'm Kyle
identify.call(you); // Hello,I'm Reader

This simple chestnut can reuse the function identify in different objects without writing a new function for each object.

Problems solved by this:

this provides a more elegant way to implicitly 'pass' a reference to an object, so you can design the API more concise and easy to reuse.

Four binding rules:

Default binding:

Rule: in non strict mode, this bound by default points to the global object, and in strict mode, this points to undefined

function foo() {
  console.log(this.a); // this points to the global object
}
var a = 2;
foo(); // 2
function foo2() {
  "use strict"; // The strict mode this is bound to undefined
  console.log(this.a); 
}
foo2(); // TypeError:a undefined

The default binding rules are like the above chestnuts. The book also mentions a subtle detail:

function foo() {
  console.log(this.a); // foo function is not a strict mode. It binds global objects by default
}
var a = 2;
function foo2(){
  "use strict";
  foo(); // Calling other functions in strict mode does not affect the default binding
}
foo2(); // 2

Therefore: for the default binding, it is the function body in strict mode that determines whether this binding object is in strict mode, strictly pointing to undefined and not strictly pointing to global objects.

Usually, strict mode and non strict mode are not mixed in the code, so this situation is very rare. Just know it and avoid digging holes in some abnormal interview questions.

Implicit binding:

Rule: whether the function has a context object at the calling position. If so, this will be implicitly bound to this object.

    function foo() {
      console.log(this.a);
    }
    var a = "Oops, global";
    let obj2 = {
      a: 2,
      foo: foo
    };
    let obj1 = {
      a: 22,
      obj2: obj2
    };
    obj2.foo(); // 2 this points to the object that calls the function
    obj1.obj2.foo(); // 2 this refers to the object calling the function at the last layer
    
    // Implicit binding missing
    let bar = obj2.foo; // bar is just a function. Its alias is obj2 A reference to foo
    bar(); // "Oops, global" - points to the global

Implicit binding missing:

The problem of missing implicit binding: in fact, when a function is called, there is no context object, but only a reference to a function, which will lead to the loss of implicit binding.

The same problem also occurs in the incoming callback function, which is more common and hidden, similar to:

    test(obj2.foo); // The reference of the incoming function also has no context object when calling.

Explicit binding:

As we have seen above, if we simply use implicit binding, we will certainly not be able to get the desired binding. Fortunately, we can also force a function call on an object to bind this object.

Rule: we can bind this in the function to the specified object through apply, call and bind.

function foo() {
    console.log(this.a);
}
let obj = {
    a: 2
};
foo.call(obj); // 2

The object passed in is not:

If you pass in an original value (string, boolean type, numeric type) as the binding object of this, the original value is converted into its object form.

If you pass null or undefined as the binding object of this into call/apply/bind, these values will be ignored when calling, and the default binding rules are actually applied.

new binding:

It is mentioned in the book: in js, there is no so-called 'constructor', only the 'construction call' of the function.

What will you do during new

  1. Create a new object.
  2. The new object will be connected by [[Prototype]].
  3. This new object will be bound to this of the function call.
  4. If the function does not return another object, the function call in the new expression will automatically return the new object.

Rule: when using construction call, this will be automatically bound to the object created during new.

function foo(a) {
  this.a = a; // Bind this to bar
}
let bar = new foo(2);
console.log(bar.a); // 2

this is the priority of the four binding rules

If multiple rules are applied at a certain calling location, how to determine which rule is effective?

    obj.foo.call(obj2); // this points to obj2. Explicit binding takes precedence over implicit binding.
    new obj.foo(); // thsi points to the newly created object new. New binding takes precedence over implicit binding.

Explicit binding and new binding cannot be directly compared (an error will be reported). The default binding is the bottom binding without applying other rules, so the priority is the lowest. The final result is:

Explicit binding > implicit binding > default binding

new binding > implicit binding > default binding

The arrow function's this point does not use the above four rules:

function foo() {
  return () => {
    console.log(this.a);
  };
}
let obj1 = {
  a: 2
};
let obj2 = {
  a: 22
};
let bar = foo.call(obj1); // foo this points to obj1
bar.call(obj2); // Output 2: execute the arrow function here and try to bind this to obj2

It can be concluded from the above chestnuts that the this rule of the arrow function:

  1. This in the arrow function inherits from the this point of the first function that is not an arrow function.
  2. Once this of the arrow function is bound to the context, it will not be changed by any code.

Keywords: Javascript this

Added by edawg on Mon, 07 Feb 2022 22:37:58 +0200