The this keyword is one of the most complex mechanisms in JavaScript. It is a special keyword that is automatically defined in the scope of all functions, but many JvaScript developers do not know exactly what it refers to. I heard you know a lot about this. Is that true?

More articles can be found at github.com/YvetteLau/B…

Answer the first question: How do you know exactly what this refers to? 【 Frequently asked Interview questions 】

[Image from the network, invaded delete]

Let’s do another one. What is the value printed out by the console? [Browser runtime environment]

var number = 5;
var obj = {
    number: 3.fn1: (function () {
        var number;
        this.number *= 2;
        number = number * 2;
        number = 3;
        return function () {
            var num = this.number;
            this.number *= 2;
            console.log(num);
            number *= 3;
            console.log(number); }}}) ()var fn1 = obj.fn1;
fn1.call(null);
obj.fn1();
console.log(window.number);
Copy the code

If the result of your thinking is the same as the result of your browsing, and the basis for each step is clear, then you can choose to continue reading, or close the page and have fun. If you’re having trouble guessing, or aren’t sure about your answer, read on.

After all, it’s worth spending an hour or two figuring this out, right?

This article will explain the binding rules for this in detail, and will analyze the previous two questions at the end.

Why do we learn this?

First of all, why do we learn this?

  1. This is used so often that if we don’t understand this, it will be difficult to read other people’s code or source code.
  2. At work, I abuse this without understanding what this refers to, which leads to problems, but I don’t know what the problem is. [I have handled this issue for at least 10 developers in my company]
  3. Using this properly allows us to write concise and reusable code.
  4. I’m sorry if you don’t answer it well.

For whatever purpose, we need to get this straight.

OK, Let ‘s go!

What is this?

Anyway, what is this? First of all, remember that this does not refer to itself! This is just a pointer to the object that called the function. We all know this saying, but in many cases, we may not be able to determine exactly what this refers to. It’s like we hear a lot of things and we still can’t live a good life. We’re not going to talk about how to live a good life today, but hopefully, after reading the following, you’ll be able to see at a glance what this refers to.

In order to see at a glance what this points to, we first need to know what the binding rules for this are.

  1. The default binding
  2. Implicit binding
  3. Hard binding
  4. The new binding

You may or may not have heard of these nouns, but keep them in mind after today. We’re going to parse them in turn.

The default binding

The default binding, the default rule used when no other binding rule can be applied, is usually a stand-alone function call.

function sayHi(){
    console.log('Hello,'.this.name);
}
var name = 'YvetteLau';
sayHi();
Copy the code

When Hi() is called, the default binding is applied. This refers to the global object (in non-strict mode). In strict mode, this refers to undefined, and undefined has no this object on it, throwing an error.

The above code, if run in a browser environment, will result in Hello,YvetteLau

But if you run it in a Node environment, the result is Hello,undefined. This is because the name in node is not hung on the global object.

In this article, unless otherwise noted, the default is the browser environment execution result.

Implicit binding

A call to a function is triggered on an object, i.e. a context object exists at the call location. The typical form is xxx.fun (). Let’s look at a piece of code:

function sayHi(){
    console.log('Hello,', this.name);
}
var person = {
    name: 'YvetteLau',
    sayHi: sayHi
}
var name = 'Wiliam';
person.sayHi();
Copy the code

It prints Hello,YvetteLau.

The sayHi function is declared externally and does not technically belong to Person, but when sayHi is called, the calling location refers to the function using the person context, and the implicit binding binds the this in the function call (in this case, sayHi) to the context object (in this case, Person).

Note that only the last layer in the chain of object properties affects the call location.

function sayHi(){
    console.log('Hello,', this.name);
}
var person2 = {
    name: 'Christina',
    sayHi: sayHi
}
var person1 = {
    name: 'YvetteLau',
    friend: person2
}
person1.friend.sayHi();
Copy the code

The result: Hello, Christina.

Because only the last layer is going to determine what this refers to, no matter how many layers there are, we’re only going to focus on the last layer, which is friend here.

One of the big pitfalls of implicit binding is that it’s easy to lose the binding (or to mislead us into thinking that this refers to something, but it doesn’t).

function sayHi(){
    console.log('Hello,'.this.name);
}
var person = {
    name: 'YvetteLau'.sayHi: sayHi
}
var name = 'Wiliam';
var Hi = person.sayHi;
Hi();
Copy the code

The result: Hello,Wiliam.

The reason for this is that Hi is a direct reference to sayHi. When called, it has nothing to do with person. For this kind of problem, I suggest you just keep this format in mind: xxx.fn (); If there is nothing before fn(), then it is definitely not an implicit binding.

In addition to this loss, implicit binding loss occurs in callbacks (event callbacks are one of them). Let’s look at the following example:

function sayHi(){
    console.log('Hello,'.this.name);
}
var person1 = {
    name: 'YvetteLau'.sayHi: function(){
        setTimeout(function(){
            console.log('Hello,'.this.name); }}})var person2 = {
    name: 'Christina'.sayHi: sayHi
}
var name='Wiliam';
person1.sayHi();
setTimeout(person2.sayHi,100);
setTimeout(function(){
    person2.sayHi();
},200);

Copy the code

The result is:

Hello, Wiliam
Hello, Wiliam
Hello, Christina
Copy the code
  • The first output is easy to understand. In the setTimeout callback function, this uses the default binding. In non-strict mode, it executes the global object

  • Is the second output a little confusing? “This” in fun refers to “XXX” when saying “XXX. Fun ()”, why not this time? Why?

    SetTimeout (fn,delay){fn(); }, which is equivalent to assigning person2.sayhi to a variable, and then executing the variable, at which point this in sayHi obviously has nothing to do with person2.

  • The third one is also in the setTimeout callback, but we can see that this is performed by person2.sayhi () using an implicit binding, so this refers to Person2 and has nothing to do with the current scope.

You may be a little tired reading this, but promise me you won’t give up, okay? Just hang in there a little bit and you’ll get the hang of it.

Explicitly bound

An explicit bind is a call,apply, or bind that explicitly specifies which object this refers to. (Note: In Javascript you don’t know, bind is treated as a hard binding.)

The first argument to call,apply, and bind is the object to which this refers. Call is the same as apply, but the parameters are passed differently. Both call and apply execute the corresponding function, but bind does not.

function sayHi(){
    console.log('Hello,'.this.name);
}
var person = {
    name: 'YvetteLau'.sayHi: sayHi
}
var name = 'Wiliam';
var Hi = person.sayHi;
Hi.call(person); //Hi.apply(person)
Copy the code

The output is: Hello, YvetteLau. Because the hard binding explicitly binds this to person.

So, does the use of hard binding mean that the loss of binding encountered by implicit binding does not occur? Clearly not so, do not believe, continue to read.

function sayHi(){
    console.log('Hello,'.this.name);
}
var person = {
    name: 'YvetteLau'.sayHi: sayHi
}
var name = 'Wiliam';
var Hi = function(fn) {
    fn();
}
Hi.call(person, person.sayHi); 
Copy the code

The output is Hello, Wiliam. The reason is simple: hi.call (person, person.sayhi) does bind this to this in Hi. However, when fn is executed, the sayHi method is called directly (remember: person.sayHi is already assigned to fn, and the implicit binding is lost) without specifying the value of this, which corresponds to the default binding.

Now, we want the binding not to get lost, so what do we do? It’s easy. When you call FN, you also hard bind it.

function sayHi(){
    console.log('Hello,'.this.name);
}
var person = {
    name: 'YvetteLau'.sayHi: sayHi
}
var name = 'Wiliam';
var Hi = function(fn) {
    fn.call(this);
}
Hi.call(person, person.sayHi);
Copy the code

At this point, the output is: Hello, YvetteLau, because person is bound to this in the Hi function, and fn binds that object to the function of sayHi. In this case, the “this” in sayHi refers to the Person object.

Now that the revolution is almost over, let’s look at the final binding rule: new binding.

The new binding

JavaScript, unlike C++, does not have classes. In javaScript, constructors are just functions that are called using the new operator. These functions are not different from normal functions, they do not belong to a class, and it is impossible to instantiate a class. Any function can be called with new, so there is no constructor, only a “construct call” to the function.

Calling a function with new automatically does the following:

  1. Creates an empty object to which this in the constructor refers
  2. This new object executes the [[prototype]] connection
  3. The constructor method is executed, and the properties and methods are added to the object referenced by this
  4. Return this, the new object created for this, if no other object is returned in the constructor; otherwise, return the object returned in the constructor.
function _new() {
    let target = {}; // Create a new object
    // The first argument is the constructor
    let [constructor. args] = [...arguments]; // Execute the [[prototype]] connection; The target isconstructorTarget.__proto__ =constructor.prototype; // Execute the constructor to add properties or methods to the created empty object let result =constructor.apply(target, args);
    if (result && (typeof (result) == "object" || typeof (result) == "function")) {
        // If the structure that the constructor executes returns an object, then return that object
        return result;
    }
    // If the constructor does not return an object, return the new object created
    return target;
}
Copy the code

So, when we call a function with new, we bind the new object to this of the function.

function sayHi(name){
    this.name = name;
	
}
var Hi = new sayHi('Yevtte');
console.log('Hello,', Hi.name);
Copy the code

The output is Hello, Yevtte, because in var Hi = new sayHi(‘Yevtte’); In this step, this in sayHi is bound to the Hi object.

Binding priority

We know that this has four binding rules, but what if we apply more than one at a time?

Obviously, we need to know which bindings have higher priority. The four bindings have priority:

New binding > Explicit binding > Implicit binding > Default binding

How to get this rule, if you are interested, you can write a demo to test, or remember the above conclusion.

Bind the exception

There are always exceptions to this rule.

If we pass null or undefined to call, apply, or bind as the binding object to this, these values will be ignored and the default binding rules will be applied.

var foo = {
    name: 'Selina'
}
var name = 'Chirs';
function bar() {
    console.log(this.name);
}
bar.call(null); //Chirs 
Copy the code

The output is Chirs because the default binding rules are actually applied.

Arrow function

The arrow function is a new addition to ES6. It is a little different from normal functions. The arrow function does not have its own this, which inherits from this in the outer code base. When using the arrow function, note the following:

(1) The this object in the function body inherits the this of the outer code block.

(2) Can not be used as a constructor, that is, you can not use the new command, otherwise an error will be thrown.

(3) Do not use arguments. This object does not exist in the function body. If so, use the REST parameter instead.

(4) The yield command cannot be used, so the arrow function cannot be used as a Generator function.

(5) The arrow function does not have its own this, so you cannot use call(), apply(), bind(), or bind() to change the point of this.

OK, so let’s see what is this for the arrow function?

var obj = {
    hi: function(){
        console.log(this);
        return (a)= >{
            console.log(this); }},sayHi: function(){
        return function() {
            console.log(this);
            return (a)= >{
                console.log(this); }}},say: (a)= >{
        console.log(this); }}let hi = obj.hi();  // Output an obj object
hi();               // Output an obj object
let sayHi = obj.sayHi();
let fun1 = sayHi(); / / output window
fun1();             / / output window
obj.say();          / / output window
Copy the code

So why? If you say that this in the arrow function is the object that you defined, that’s not what you would expect, because by definition, this in say should be obj.

Let’s analyze the above execution results:

  1. obj.hi(); This corresponds to the implicit binding rule for this, this is bound to obj, so it prints obj, which makes sense.
  2. hi(); So what we’re doing here is we’re executing the arrow function, and the arrow function inherits this from the previous code base, and we just figured out that this at the previous level was obj, so obviously this is obj here.
  3. Perform sayHi (); This step is also easy to understand. We talked about this implicit missing binding, where this executes the default binding and this refers to the global object Window.
  4. fun1(); This step is executing the arrow function, so if this refers to the object where the arrow function was defined, then this doesn’t make sense. OK, it makes sense that the arrow function this is inherited from the outer code base. The outer code base that we just analyzed, this refers to window, so the output here is window.
  5. obj.say(); “This” is not present in obj, so you can only look up and find the global “this”, pointing to window.

Student: You said the this of the arrow function is static?

It’s still the same code. Let’s see if this in the arrow function is really static?

I say: no

var obj = {
    hi: function(){
        console.log(this);
        return (a)= >{
            console.log(this); }},sayHi: function(){
        return function() {
            console.log(this);
            return (a)= >{
                console.log(this); }}},say: (a)= >{
        console.log(this); }}let sayHi = obj.sayHi();
let fun1 = sayHi(); / / output window
fun1();             / / output window

let fun2 = sayHi.bind(obj)();/ / output obj
fun2();                      / / output obj
Copy the code

As you can see, fun1 and fun2 correspond to the same arrow function, but the output of this is different.

So, keep in mind that the arrow function does not have its own this; the this in the arrow function inherits from the this in the outer code base.

conclusion

I’ve come to the end of the rule for this, but it takes some training to see what this is bound to at a glance.

So let’s go back to the original question.

1. How to determine exactly what this refers to?

  1. Is the function called in new (new binding)? If so, then this is bound to the newly created object.
  2. Is the function called by call,apply, or hard bind? If so, then this binds to the specified object.
  3. Whether the function is called in a context object (implicit binding), if so, this is bound to that context object. Usually obj. Foo ()
  4. If none of the above, use the default binding. If in strict mode, it is bound to undefined, otherwise to a global object.
  5. If null or undefined is passed to call, apply, or bind as the binding object to this, these values are ignored when called, and the default binding rules are applied.
  6. In the case of an arrow function, the this of the arrow function inherits from the this of the outer code block.

2. Perform process analysis

var number = 5;
var obj = {
    number: 3,
    fn: (function () {
        var number;
        this.number *= 2;
        number = number * 2;
        number = 3;
        return function () {
            var num = this.number;
            this.number *= 2;
            console.log(num);
            number *= 3;
            console.log(number);
        }
    })()
}
var myFun = obj.fn;
myFun.call(null);
obj.fn();
console.log(window.number);
Copy the code

So let’s look at the execution of this code.

  1. When obj is defined, the fn closure is executed, and the function is returned. When the closure is executed, the new binding (no new keyword) is not applied, and the hard binding (no call,apply,bind) is not applied. Obviously not. If xx.fn () is not there, then the implicit binding is certainly not applied, so the default binding is applied. In non-strict mode this is bound to the window (the browser execution environment). This is not the same as the lexical scope unless it is an arrow function.
window.number * = 2; // the value of window.number is 10(var number defines a global variable that is hung on the window)

number = number * 2; //number is NaN; Notice that we defined a number here, but we didn't assign a value, so number is undefined; Number(undefined)->NaN

number = 3;          // The value of number is 3
Copy the code
  1. myFun.call(null); As we said earlier, null is passed as the first argument to call, which calls the default binding;
fn: function(){
    var num = this.number;
    this.number *= 2;
    console.log(num);
    number *= 3;
    console.log(number);
}
Copy the code

Execution time:

var num = this.number; //num=10; Now this is pointing to window
this.number * = 2;     //window.number = 20
console.log(num);      // The output is 10
number *= 3;           //number=9; This number corresponds to the number in the closure; The number in the closure is 3
console.log(number);   // The output is 9
Copy the code
  1. obj.fn(); Implicit binding is applied. This in FN corresponds to obj.
var num = this.number;//num = 3; So this is going to be obj
this.number *= 2;     //obj.number = 6;
console.log(num);     // The output is 3;
number *= 3;          //number=27; This number corresponds to the number in the closure; The number in the closure is now 9
console.log(number);  // The output is 27
Copy the code
  1. Last step console.log(window.number); The output is 20

Therefore, the result in the group is:

10, 9, 3, 27, 20Copy the code

Strict model results, according to what we learned today, their own analysis, consolidate the knowledge.

In the end, congratulations to those who have stuck it out. You have managed to get this knowledge, but to fully master it, you need to review and practice more. If you have a good example of this, please leave us a comment in the comments section, and we’ll work together!

Thank you for spending your precious time to read this article. If this article gives you some help or inspiration, please don’t hesitate to give me your likes and stars. Your praise is definitely the biggest motivation for me to move forward. Github.com/YvetteLau/B…

Thanks for pointing out the additional reference link as follows:

  • JavaScript Books You Don’t Know
  • ES6 Documentation – Arrow functions (es6.ruanyifeng.com/#docs/funct…)
  • You can leave me a message if you have a link to it.

Follow the public account and join the technical exchange group