Understand this thoroughly

Posted by Xoom3r on Wed, 03 Jun 2020 07:45:05 +0200

This is one of the most commonly used keywords when we write code. Even so, it is also the most easily headache keyword for JavaScript. So what is this?

If you know the execution context, you will know that this is actually an attribute of the execution context object:

executionContext = {
    scopeChain:[ ... ],
    VO:{
        ...
    },
    this:  ? 
}

There are three important attributes in the execution context: scope chain, variable object and this.

this is determined when entering the execution context, that is, when the function is executed, and is not allowed to be modified during runtime and is permanent

this in global code

This is the same in global code, and this is always the global object itself.

var a = 10; 
this.b = 20;
window.c = 30;

console.log(this.a);
console.log(b);
console.log(this.c);

console.log(this === window) // true
// Because this is the global object window, a, B and C above are equivalent to adding corresponding attributes to the global object

If we try to change the value of this during the code run time, an error will be thrown:

this = { a : 1 } ; // Uncaught SyntaxError: Invalid left-hand side in assignment
console.log(this === window) // true

this in function code

Using this in function code is the most confusing thing for us. Here we mainly analyze this in function code.

We said above that the value of this is determined when entering the current execution context, that is, when the function is executed and before execution. However, the direction of this in the scope of the same function may be completely different, but in any case, the direction of this in the runtime of the function is unchanged and cannot be assigned.

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

foo();  // window
var obj={
    a: 1,
    bar: foo,
}
obj.bar(); // obj

This can be a global object, a current object, or an arbitrary object, depending on how the function is called. There are several ways to call a function in JavaScript: as a function, as an object property, as a constructor, using apply or call. We will discuss the meaning of this one by one according to these calling methods.

Call as function

What is called as a function: it is an independent function call without any modifiers.

function foo(){
    console.log(this === window); // true
    this.a = 1;
    console.log(b); // 2
}
var b = 2;
foo();
console.log(a); // 1

In the above code, this is bound to the global object window. this.a is equivalent to adding an attribute a to the global object.

In strict mode, the binding of this is no longer window, but undefined.

function foo() {
    "use strict";
    console.log(this===window); // false
    console.log(this===undefined); // true
}
foo();

Note here that if the function call is in strict mode and the internal code is executed in non strict mode, this will still be bound to window by default.

function foo() {
    console.log(this===window); // true
}


(function() {
    "use strict";
    foo();
})()

Who does this point to when the function inside the function calls this independently?

function foo() {
    function bar() {
        this.a=1;
        console.log(this===window); // true
    }
    bar()
}
foo();
console.log(a); // 1

In the above code, the function inside the function is called independently, and this is still bound to the window.

Summary: when a function is called as an independent function, the internal this is bound to (point to) the global object window by default, but there will be differences in strict mode. In strict mode, this is bound to defined.

Called as an object property

var a=1;
var obj={
    a: 2,
    foo: function() {
        console.log(this===obj); // true
        console.log(this.a); // 2
    }
}
obj.foo();

The value of the foo attribute in the above code is a function. Here foo is called the method of object obj. Foo is called by object. Method. At this point, this is bound to the object of the current calling method. Here is the obj object.

Let's take another example:

var a=1;
var obj={
    a: 2,
    bar: {
        a: 3,
        foo: function() {
            console.log(this===bar); // true
            console.log(this.a); // 3
        }
    }
}
obj.bar.foo();

Follow the above rule object. Property. The object here is obj.bar  . At this time, foo internal this is bound to obj.bar . So this.a is obj.bar.a .   

Let's take another example:

var a=1;
var obj={
    a: 2,
    foo: function() {
        console.log(this===obj); // false
        console.log(this===window); // true
        console.log(this.a); // 1
    }
}

var baz=obj.foo;
baz();

Here foo function is used as the method of object obj. But it is assigned to the variable baz. When baz is called, it is equivalent to foo function calling independently, so the internal this is bound to window.

Call with apply or call

apply and call are the methods on the function prototype. It can change the direction of this inside the function.

var a=1;
function foo() {
    console.log(this.a);
}
var obj1={
    a: 2
}
var obj2={
    a: 3
}
var obj3={
    a: 4
}
var bar=foo.bind(obj1);
bar();// 2  this => obj1
foo(); // 1  this => window
foo.call(obj2); // 3  this => obj2
foo.call(obj3); // 4  this => obj3

When the function foo is called as an independent function, this is bound to the global object window. When using the bind, call or apply method call, this is bound to different objects.   

Called as constructor

var a=1;
function Person() {
    this.a=2;  // this => p;
}
var p=new Person();
console.log(p.a); // 2

In the above code, this inside the constructor Person is bound to an instance of Person.

Summary:

When we want to determine this binding within the current function, we can follow the following principles:

  • Is the function called through the new operator? If so, this is bound to the newly created object
var bar = new foo();     // this => bar;
  • Is the function called through call or apply? If yes, this is bound to the specified object
foo.call(obj1);  // this => obj1;
foo.apply(obj2);  // this => obj2;
  • Is the function called through an object. Method? If yes, this is bound to the current object
obj.foo(); // this => obj;
  • Is the function called independently? If so, this is bound as a global object.
foo(); // this => window

this in DOM event handler

1) . event binding

<button id="btn">Click on me</button>

// Event binding

function handleClick(e) {
    console.log(this); // < button id = "BTN" > Click me < / button >
}
document.getElementById('btn').addEventListener('click',handleClick,false);  //   < button id = "BTN" > Click me < / button >
        
document.getElementById('btn').onclick= handleClick; //  < button id = "BTN" > Click me < / button >

According to the above code, we can conclude that when events are added to DOM elements through event binding, the events will be bound to the current DOM object.

2) . inline events

<button onclick="handleClick()" id="btn1">Click on me</button>
<button onclick="console.log(this)" id="btn2">Click on me</button>

function handleClick(e) {
    console.log(this); // window
}

//The second button prints < button id = "BTN" > click on me < / button >

I think inline events can be understood as follows:

//Pseudocode

<button onclick=function(){  handleClick() } id="btn1">Click on me</button>
<button onclick=function() { console.log(this) } id="btn2">Click on me</button>

In this way, we can understand why one of the inline events in the above code points to window and the other to the current DOM element. (of course, this is not the case when browsers handle inline events.)

this in timer

Where does this in the timer point to?

function foo() {
    setTimeout(function() {
        console.log(this); // window
    },1000)
}
foo();  

Let's take another example

var name="chen";
var obj={
    name: "erdong",
    foo: function() {
        console.log(this.name); // erdong
        setTimeout(function() {
            console.log(this.name); // chen
        },1000)
    }
}
obj.foo();

Here we can see that this inside the function foo points to the object that calls it, namely: obj. This in the timer points to window. So what's the way to bind this in the timer to the function that wraps it?

1) . using closures:

var name="chen";
var obj={
    name: "erdong",
    foo: function() {
        console.log(this.name)
        var that=this;
        setTimeout(function() {
            console.log(that.name); // window
        },1000)
    }
}
obj.foo();

By using the characteristics of closure, the function inside the function can access the meaning to access the variables in the current lexical scope. At this time, that in the timer is the object bound by this in the function that wraps it. In the following we will introduce how to use the arrow function of ES6 to achieve this function.

Of course, bind can also be applied here:

var name="chen";
var obj={
    name: "erdong",
    foo: function() {
        console.log(this.name);
        setTimeout(function() {
            console.log(this.name); // window
        }.bind(this),1000)
    }
}
obj.foo();

this ignored

If you pass null or undefined as the binding object of this into call, apply or bind, these values will be ignored when calling. The instance this is bound to the above rules.

var a=1;
function foo() {
    console.log(this.a); // 1  this => window
}
var obj={
    a: 2
}
foo.call(null);

 

var a=1;
function foo() {
    console.log(this.a); // 1  this => window
}
var obj={
    a: 2
}
foo.apply(null);

  

var a=1;
function foo() {
    console.log(this.a); // 1  this => window
}
var obj={
    a: 2
}
var bar = foo.bind(null);
bar();

bind can also realize function currification:

function foo(a,b) {
    console.log(a,b); // 2  3
}
var bar=foo.bind(null,2);
bar(3);

More complex examples:

 var foo={
    bar: function() {
        console.log(this);
    }
};

foo.bar(); // foo
(foo.bar)(); // foo

(foo.bar=foo.bar)(); // window
(false||foo.bar)();  // window
(foo.bar,foo.bar)();  // window

In the above Codes:

foo.bar() is the method call of the object, so this is bound to a foo object.

( foo.bar )() the content in the previous () is not calculated, so it is still foo.bar()

( foo.bar=foo . bar () the content in the previous () is function (){ console.log (this);} so here is anonymous function self execution, so this is bound to global object window

The last two examples are the same as above.

This is a better understanding:

( foo.bar=foo.bar) the expressions in parentheses are evaluated, assigned, and returned.
(false|| foo.bar )() the expression in parentheses is executed to determine whether the former is true, if it is true, the latter is not evaluated, if it is false, the latter is evaluated and the value of the latter is returned.
( foo.bar , foo.bar )The expressions in parentheses act on both sides of the "," operator, and then return the values after the "," operator.

this in arrow function

 

The new syntax of ES6 is arrow function.

There are two functions:

  1. More concise functions
  2. this is not bound by itself

The code format is:

// Ordinary function
function foo(a){
    // ......
}
//Arrow function
var foo = a => {
    // ......
}

//If there are no parameters or multiple parameters

var foo = (a,b,c,d) => {
    // ......
}

Before using a normal function, we need to determine the binding object of this within the function according to how the function is called. And often because of the number of call chains or the inability to find their real callers, the direction of this is ambiguous. After the arrow function appears, the direction of this inside does not need to be determined by the way of calling.

Arrow function has several characteristics (different from ordinary function)

  1. The arrow function does not bind this. It only inherits this from the upper level of the scope chain.
  2. Instead of binding arguments, the arrow function uses the reset parameter to get the number of arguments.
  3. The arrow function is anonymous and cannot be used as a constructor.
  4. The arrow function does not have a prototype property.
  5. The yield keyword cannot be used, so arrow functions cannot be function generators.

Here we only discuss this binding in arrow function.

Use an example to compare this binding between ordinary functions and arrow functions:

var obj={
    foo: function() {
        console.log(this); // obj
    },
    bar: () => {
        console.log(this); // window
    }
}
obj.foo();
obj.bar();

In the above code, a function is also called through an object. Method, but the binding of this inside the function is really different, only because an ordinary function is an arrow function.

In one sentence, summarize this binding in arrow function:

It's not true that what I said above will inherit this from the upper level of the scope chain. The scope stores the collection of variable objects of the function's current execution context and all parent execution contexts. So there is no this in the scope chain. It should be said that this is inherited from the execution context corresponding to the layer on the scope chain.

This in the arrow function inherits this in the execution context corresponding to the layer on the scope chain

var obj={
    foo: function() {
        console.log(this); // obj
    },
    bar: () => {
        console.log(this); // window
    }
}
obj.bar();

In the above code obj.bar The scope chain at execution time is:

scopeChain = [
    obj.bar.AO,
    global.VO
]

According to the above rules, this in the bar function points to this in the global execution context, that is, window.

Let's take another example:

var obj={
    foo: function() {
        console.log(this); // obj
        var bar=() => {
            console.log(this); // obj
        }
        bar();
    }
}
obj.foo();

In normal functions, this is bound as a global object when bar executes, because it is called as an independent function. But in the arrow function, it is bound to obj. Bind to the same object as this in the parent function.

At this time, its scope chain is:

 scopeChain = [
     bar.AO,
     obj.foo.AO,
     global.VO
 ]

At this time, we almost know this binding in arrow function.

Let's continue with the example:

var obj={
    foo: () => {
        console.log(this); // window
        var bar=() => {
            console.log(this); // window
        }
        bar();
    }
}
obj.foo();

How can I point to window again at this time?

We also look at the scope chain when bar executes:

 scopeChain = [
     bar.AO,
     obj.foo.AO,
     global.VO
 ]

When we look for this binding in bar function, we will look for this binding in foo function. Because it is inherited from it. At this time, foo function is also an arrow function. At this time, this in foo is bound to window instead of calling its obj object. Therefore, this binding in the bar function is also the global object window.

Let's go back to the above example about this in timer:

var name="chen";
var obj={
    name: "erdong",
    foo: function() {
        console.log(this.name); // erdong
        setTimeout(function() {
            console.log(this); // chen
        },1000)
    }
}
obj.foo();

At this time, we can simply bind this in timer to this in foo as the same object:

var name="chen";
var obj={
    name: "erdong",
    foo: function() {
        console.log(this.name); // erdong
        setTimeout(() =>  {
            console.log(this.name); // erdong
        },1000)
    }
}
obj.foo();

  

If you need to reprint, please indicate the source.  

Topics: Javascript Attribute