preface

This is a very important concept in JavaScript because we use it so often that while we enjoy its convenience, its binding rules are somewhat difficult to understand. Today we will sum up the relevant knowledge points of this, so that in the process of using it can be more confident and grasp!

Look at thethis

As always, before we conclude, we need to know, what is this?

thisWhat is the

Let’s start with an example:

    function foo() {
      console.log(this);	//Window
    }
    foo();
Copy the code

We define a function foo and execute it. In the function body we print this directly and find that the result is not undefined, but the Window object. Why is that? This leads to the related concept:

This is a special keyword that is automatically defined in the scope of all functions.

It’s just a conceptual cloud, but it makes two key points:

  1. thisIs aThe keyword
  2. thisbeAutomatic definitionIn the scope of all functions

Let’s analyze it separately:

1. thisIs a keyword

In fact, this is a keyword, but there are some special, specific special we will introduce the following. Since this is a keyword, we need to note that it should have one feature: this cannot be overridden. Let’s try modifying the previous example:

    function foo() {
      this = "null"; //Uncaught SyntaxError
      console.log(this);
    }
    foo();
Copy the code

As you can see, the value of this cannot be modified or an error will be reported.

2. thisbeAutomatic definitionIn the scope of all functions

This keyword is automatically defined in function scope. That’s why we just printed this in foo to get the Window object.

All function scopes include the this keyword. But that doesn’t mean that only functions have this. What if we tried to print this globally?

console.log(this);	//Window
Copy the code

As you can see, the Window object is also output, indicating that the this keyword also exists in the global scope.

So now you might be wondering why in all of these examples, the value of this is the Window object, is this the same value as the Window object? Let’s look at another example:

    var obj = {
      say: function () {
        console.log(this); }}; obj.say();/ / {say: ƒ}
Copy the code

The value of this is not the same as that of the Window object. The value of this is not the same as that of the Window object.

thisPoint to the value of the

In fact, as we mentioned earlier when we introduced the execution context (click here if you are interested), when we dynamically create the execution context, we confirm that This Binding is the value of This, which has nothing to do with the position of the function definition, but is determined by the Binding rule when the function is called.

That’s why, in the global case, the value of this also exists, precisely because there is a global context and the value of this is stored in that context.

So who does this refer to and what are the binding rules for function calls? That’s what we’re going to talk about.

The binding rule for this

We just said that the value of this is determined by the binding rules of the function call, so let’s talk about what those binding rules are.

The default binding

The default binding is the most basic binding rule, applied when no other rules apply, and is therefore the most common binding rule. A typical judgment of default binding is that only the default binding, and no other binding rule, can be used when a call is made using an undecorated function reference. Here’s an example:

function foo(){
	console.log(this);	//Window
}
foo();
Copy the code

As you can see, foo() here is a function call without any decorations; foo has nothing in front of it. And you’ll notice that the output value of this is the Window object, so you already know that this is probably the default value to which this points, but it’s not exactly the default value to which this points. In fact, the default value to which this points can be different:

  • Strict mode

In strict mode, using the default rule, this will point to undefined. Look at the code:

'use strict'
function foo(){
	console.log(this);	//undefined
}
foo();
Copy the code
  • Nonstrict mode

In non-strict mode, if the default rules are applied, the value of this will refer to the Window object. That’s why we’ve just given several examples of this being Window. Here’s the code:

function foo(){
	console.log(this);	//Window
}
foo();
Copy the code
  • Strict mode and non-strict mode are mixed

If we define a function in non-strict mode and call it in strict mode, the value of this will still refer to the Window object. Look at the code:

    function foo() {
      console.log(this);	//Window} {("use strict");
      foo();
    }
Copy the code

Implicit binding

Having covered the default binding rules, it’s time to look at some special binding rules. When a function is called as a method on an object that is the context object of the function, this refers to that object. Here’s an example:

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

As you can see, we define a method foo on the object obj and call it as obj.foo(). The obj object is the context object in which foo is called, so this refers to obj. We call this an implicit binding. Implicit binding has several important points to highlight:

Implicit loss

Implicitly bound functions, which in some cases lose the bound context object, apply our default binding rules by referring this to the Window object (non-strict mode) or undefined (strict mode). Let’s start with an example:

    var name = "I'm the global name.";
    var obj = {
      name: "I'm obj's name.".foo: function () {
        console.log(this.name); // I am the global name}};let fn = obj.foo; // foo is stored in the fn variable
    fn();
Copy the code

Here we use a variable fn to hold a reference to foo under obj. In this case, the output is the value of the name variable defined under the global object.

When we execute let fn = obj.foo, we are actually pointing fn to the address of function foo, so when we execute fn() we are actually executing foo(), and the code above looks like this:

    var name = "I'm the global name.";
    function foo() {
      console.log(this.name); // I am the global name
    }
    foo();
Copy the code

This is essentially a function call without any decoration, so the default binding rules apply.

Implicit loss is another case where an object’s method is passed as a parameter to another function. Here’s an example:

    var name = "I'm the global name.";
    var obj = {
      name: "I'm obj's name.".foo: function () {
        console.log(this.name); // I am the global name}};function otherFn(fn) {
      fn();
    }
    otherFn(obj.foo);
Copy the code

The only difference between this example and the previous one is that instead of saving foo in a variable and calling it later, we pass foo as an argument to another function and then call it in another function. Why is implicit loss happening here too?

The answer is that when a parameter is passed to a function, an assignment occurs, that is, the argument is assigned to the parameter. The function part of the code can look like this:

    function otherFn() {
      var fn = obj.foo;	// We are actually assigning the argument to the parameter
      fn();
    }
    otherFn(obj.foo);
Copy the code

This is the same as the implicit loss case above, which is a function call without any decoration, so the default rules apply and implicit loss occurs.

Object property reference chain

We just said that when a function is called as a method of an object, that object is used as the context object, so this refers to that object. What object does this refer to when multiple objects are nested together and the method is called? Let’s look at an example:

    var obj1 = {
      name: "obj1".obj2: {
        name: "obj2".obj3: {
          name: "obj3".foo: function () {
            console.log(this.name); //obj3}},}}; obj1.obj2.obj3.foo();Copy the code

Did you miss it? Don’t worry, we see obj3 as the result, and the fact that when we call a method through the object attribute call chain, the only thing that matters is the calling object in front of the function. For example, obj1.obj2.obj3.foo() can actually be viewed as obj3.foo(). The same goes for even… A.b.c.d.e.foo (), we just look at e.foo() at the end.

Explicitly bound

We just talked about implicit binding. The reference value of this is completely determined by the calling object, which is very difficult to control. Is there a way that I can specify the reference value of this that I want, no matter which object I’m calling from? Yes, there are two methods called and apply on the prototype function, which can pass in the specified object as the reference value of this, but there are some differences between the two methods. Let’s look at the usage separately.

The use of the call

For the definition and basic usage of call, see MDN: Portal

Let’s see how call explicitly changes the pointer to this:

    var obj = {
      name: "obj"};function foo() {
      console.log(this.name);	//obj
    }
    foo.call(obj);
Copy the code

The foo function calls the call method and passes in obj as the first argument, which points to the value as this and executes the function. So how does the Call method do this? So let’s do that. Let’s look at apply.

Apply the usage of the

For the definition and basic usage of apply, see MDN: Portal

The use and effect of apply is almost the same as that of call, as can be seen from examples:

    var obj = {
      name: "obj"};function foo() {
      console.log(this.name);	//obj
    }
    foo.apply(obj);
Copy the code

If you look at these two examples, they are almost identical, but there are differences. The difference lies in the way they receive parameters. Let’s look at an example:

    var obj = {
      name: "obj"};function foo(a,b,c) {
      console.log(this.name,a,b,c);	//obj 1 2 3
    }
    foo.call(obj,1.2.3);
    foo.apply(obj,[1.2.3]);
Copy the code

As you can see, the call function takes multiple arguments and passes them to foo, while the apply function takes arguments in an array and then splits them up and passes them to foo. That’s the difference.

We explicitly bind this to the reference value by calling call or apply, but you will notice that call or apply calls the function directly, and there is no way to pass the function along with this to another function for storage or execution. Can we bind this to the function first and then wrap it around it so that it can pass from one function to another? That’s where our bind function comes in.

The use of the bind

For a definition and basic use of BIND, see MDN: Portal

The bind function can pass in the specified object as a reference to this. In addition, the bind function can take arguments and store them. The next call can pass in only the remaining arguments.

    // Again, the implicit missing example, let's use the bind function to bind the obj object
    var name = "I'm the global name.";
    var obj = {
      name: "I'm obj's name.".foo: function () {
        console.log(this.name); }};function otherFn(fn) {
      // You will find that after the function is passed in, this still refers to obj without implicit loss
      fn(); // I am obj's name
    }
    otherFn(obj.foo.bind(obj)); // Instead of passing in obj.foo directly, we'll wrap it around obj.foo
Copy the code

As you can see from this example, a function that binds this to a value by bind does not lose this object if it is passed into another function. Here’s another example of how to store parameters while binding this:

    function foo(a, b, c) {
      console.log(a, b, c);
    }

    // When the first argument is null, in non-strict mode the Window object is referred to as this, as described below
    // shell the foo function and store two arguments, 1,2
    var bindFoo = foo.bind(null.1.2);
    // When this wrapper function is called, the arguments passed in are passed to foo along with the previously stored arguments
    bindFoo(3); / / 1 2 3
Copy the code

In this way, we have implemented the prepass parameter and this value, can easily pass the value of the function ~

There’s another case where I think it’s too rigid to show the binding, and actually I want a more flexible binding, so I want to default this to point to an object, and if I accidentally apply the default binding rule and this points to Window or undefined I want this to point back to my preset object, Otherwise, refer to its original “this” object. Is that ok? Boy, there are a lot of requirements, but I have satisfied you, that is our softBind ~

The use of the softBind

The softBind function did not exist in the function prototype, but was created later, as the name implies, to fulfill the requirements we just described. Since the prototype does not exist, it is natural to explain how to define the implementation:

    Function.prototype.softBind = function (obj) {
      // Get the function that called softBind itself
      var fn = this;

      // To get the other parameters passed in and store them
      var curried = [].slice.call(arguments.1); //arguments is an array of classes so there is no slice method

      // Here is the returned wrapped function
      var bound = function () {
        
        // Check the case of this, where this is returned when the wrapper function is executed, as opposed to this when the softBind function was called
        var that = (!this || this= = = (window || global))? obj :this; // Determine whether this is empty, considering the Node environment

        // The purpose here is to merge arguments passed to the wrapper function with arguments passed to the wrapper function. Arguments are different from arguments
        var newArguments = [].concat.apply(curried, arguments);

        // Call the function
        return fn.apply(that, newArguments);
      };

      // One detail is to adjust the prototype chain of the wrapped function so that instanceof can be used to determine the wrapped function
      bound.prototype = Object.create(fn.prototype);
      return bound;
    };   
Copy the code

If you don’t understand it, think about it slowly, and then let’s see how it is used:

    var name = "I'm the global name.";
    var obj1 = {
      name: "I am the name of obj1"
    };
    var obj2 = {
      name: "I am the name of obj2"
    };

    function foo(){
    	console.log(this.name)
    }
    
    // Wrap a function that defaults this to obj1
    var fn=foo.softBind(obj);
    
    // when called through obj2, obj2 is used as this
    fn.call(obj2);	// I am obj2's name
    
    // When called without modifiers, the bound this value is applied
    fn();	// I am obj1's name
Copy the code

This gives us a flexible display binding function

The new binding

The new operator can also change the direction of this. The new operator can also change the direction of this. The new operator can also change the direction of this.

  • Create a new object and bind this to the newly created object
  • Call the constructor with the passed argument
  • Points the _proto__ of the created object to the constructor’s prototype
  • If the constructor does not explicitly return an object, the new object created is returned, otherwise the object explicitly returned (that is, the object returned manually)

Then let’s look at an example:

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

let person=new Foo('xiaowang');
console.log(person);	//xiaowang
Copy the code

As you can see, when the function executes new as a constructor, this points to the instance person that was eventually created, demonstrating that the new operator can indeed change the orientation of this.

Arrow function binding

In addition to all of the previous ones, there is a special type of function in ES6: the arrow function. The arrow function “this” is special. Its pointer value is not dynamically determined, but is determined by the “this” value contained in the function’s scope when it is defined.

    // Define an arrow function
    var foo = () = > {
      console.log(this.name);
    };
    var name = "I'm the global name.";
    var obj1 = {
      name: "I am the name of obj1"}; foo.call(obj1);// "I am the global name"
Copy the code

As you can see, even though we call obj1, the final output value is the global name, because foo is defined globally, so this points to the window object.

Priority of the binding rule

The priority of the “this” binding rule is the priority of the “this” binding rule. We’ve talked a lot about binding rules, but we haven’t talked about what the combination of these binding rules can do. When multiple binding rules are used at the same time, the higher priority binding rules will be used. What is the priority of these rules? If we order it from lowest to highest, it’s:

  1. The default binding
  2. Implicit binding
  3. According to the binding
  4. The new operator binds the arrow function

Let’s take a look at some examples to verify their priority:

  • Default and implicit binding

Just look at the first example:

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

The result, of course, is that the implicit binding’s this value is applied, so the implicit binding takes precedence over the default binding.

  • Implicit binding and display binding

Let’s take a simple example:

    var obj1 = {
      name: "obj1".foo: function () {
        console.log(this.name); }};var obj2 = {
      name: "obj2"}; obj1.foo.apply(obj2);//obj2
    obj1.foo.call(obj2); //obj2
    obj1.foo.bind(obj2)(); //obj2
Copy the code

It can also be seen that the result of the implicit binding is overwritten by the display binding, so the explicit binding takes precedence over the implicit binding.

  • Show binding and new operator binding

Since call and apply only execute functions and cannot be used with the new operator, we will only compare the precedence of the bind and new operators.

    var obj = {};
    function foo() {
      this.name = "obj";
    }
    // Bind foo to obj
    var fn = foo.bind(obj);

    // Perform the new operation
    var f1 = new foo();
    console.log(obj.name); //undefined
    console.log(f1.name); //obj
Copy the code

As you can see, foo is already explicitly bound to obj, and eventually name is assigned to f1, so the new binding takes precedence over the explicit binding.

If you’re wondering, bind has a shell for foo, and the logic of the new operator doesn’t allow you to change the reference to this. In fact, bind has a judgment inside that if it’s used with the new operator, it should give this to the new object. This also establishes the priority relationship between them.

  • The arrow function binding and the new operator binding

Since the arrow functions are not constructible, they cannot be combined with the new operator, so I put them on the same level.

Write the call, apply, bind functions by hand

The call to realize

Let’s take foo.call(obj) as an example:

  Function.prototype.myCall = function (context) {
    // Check if the passed this point is null. Context is obj
    context = context || window;

    // We give a unique attribute name to avoid overwriting
    let fn = new Symbol('fn');

    // This is the function foo, which we call as an attribute of the context object passed in, so we can set this using implicit binding rules
    context[fn] = this

    // Remove the context value passed in, and all that is left is the argument
    const args = [...arguments].slice(1);

    // Execute the function and return the result
    constresult = context.fn(... args);// Delete the property value and destroy the crime scene
    delete context.fn;

    return result;
  }
Copy the code

apply

Foo. Apply (obj); foo. Apply (obj); foo.

  Function.prototype.myApply = function (context, args) {
    // Check if the passed this point is null. Context is obj
    context = context || window;

    // We give a unique attribute name to avoid overwriting
    let fn = new Symbol('fn')

    // This is the function foo, which we call as an attribute of the context object passed in, so we can set this using implicit binding rules
    context[fn] = this

    // Execute the function and return the result... You can expand the parameters of an array
    constresult = context[fn](... args);// Delete the property value and destroy the crime scene
    delete context.fn;

    return result;
  }
Copy the code

bind

Bind (obj) : foo. Bind (obj) : foo.

Function.prototype.myBind = function (context, ... args) {
    // Again to get the function that calls bind itself, such as foo
    const fn = this

    // Make the argument null
    args = args ? args : []

    // Bind returns a closure that holds the context and parameters passed in
    return function newFn(. newFnArgs) {
      // Handle the combination of new and bind, leaving this to new
      if (this instanceof newFn) {
        return newfn(... args, ... newFnArgs) }// Otherwise, use apply directly to display the binding context target
      return fn.apply(context, [...args, ...newFnArgs])
    }
  }
Copy the code

And you’re done!

conclusion

There are a lot of summaries about this knowledge point. I don’t want to simply list the knowledge point, but want to introduce it in a systematic and hierarchical way. At the same time, I want to write my own understanding, so that the summary is more meaningful. Code so many words really not easy, I hope to help you ~ finally, after all, the content is more, there are mistakes, I hope you point out, thank you!

Write in the last

  1. Thank you very much to see here, might as well click a “like” support, very grateful ~!

  2. More articles and knowledge points will be updated in the future. If you are interested, you can pay attention to a wave of ~