The this problem is one of the most complex problems in JavaScript, and even experienced programmers can inadvertently fall into the this keyword pit. Whenever a function is defined, this is automatically defined inside the function (even if the this keyword is not used inside the function).

Function call location

Before understanding this, let’s understand function call location. The call position of a function is where the function is called, not where it is declared. In order to understand call location, it is important to analyze a function’s call stack , which is a chain of function executions, to understand the call stack and call location by following a few examples.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    function f1() {
        // 当前调用栈:f1

        f2(); // f2的调用位置

        console.log("f1");
    }

    function f2() {
        // 当前调用栈为 f1 -- >f2

        f3(); // f3的调用位置

        console.log("f2");
    }

    function f3() {
        // 当前调用栈为 f1 --> f2 --> f3

        console.log("f3");
    }

    f1();

We define three functions f1, f2 and f3, execute f1(), because f2 is called inside f1, and f3 is called inside f2, so the program will execute f2() and f3() one after another, the call stack is f1 when executing f1, when executing f2, the call stack is f1 –> f2, when executing f3, the call stack is f1 --> f2 --> f3, so the call stack is similar to a single chain table, from the head of the table to the end of the table in order of execution.

Some debugging tools have a special call stack panel that shows the call stack of the current program execution. In Google Chrome, you can press F12 to open Developer Tools and switch to the Sources page, you can see the call stack panel, which will show the call stack when the program is executed.

Binding rules

Below, I have summarized four binding rules for this. Before applying the following rules, you need to analyze the call stack to find where the function is called, and then judge to apply one of the four rules below.

Default Binding

This is the default rule for this binding, which is used when no other rule can be applied.

1
2
3
4
5
6
7
function f() {
    console.log(this.a);
}

var a = 2;

f(); // 输出:2

A variable declared in a global scope in JavaScript is a property of the same name of the global object. In the browser environment, this global object is window, so var a = 2 is equivalent to window.a = 2.

When using default binding, this is bound to the global object. In the above example, when the function f() is executed, this is default bound to the global object, i.e., this is pointing to the global object, so the output is 2.

Note that when the program is declared "use strict", the global object cannot be used for default binding.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function f() {
    "use strict";

    // 此时this绑定到undefined
    console.log(this.a);
}

var a = 2;

f();

Note: Try to use strict patterns in your code, non-strict patterns tend to write code that is difficult to maintain. And don’t use a mix of strict and non-strict patterns.

Implicit binding

When a function is executed, it belongs to some object Object, then this is bound to that object at that time.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function f() {
    console.log(this.a);
}

var obj = {
    a: 3,
    f: f
};

var a = 2;

obj.f(); // 输出:3

Although the function f is defined in the global scope, it is added to obj as a reference, and when obj.f() is executed, f ‘belongs’ to obj, so the output is 3.

Change the above example a bit.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var obj = {
    a: 3,
    f: function() {
        console.log(this.a);
    }
};

var f = obj.f;

var a = 2;

f(); // 输出:2

At this point, the output is 2. Why? Because strictly speaking functions do not belong to any one object, the obj.f and f in the above example are just a reference to a function, so f and obj.f are not directly related, just the same function referenced, so the execution of f(), at this time the execution environment in the global scope, will automatically apply the default binding rules to bind this to the global object. The second example, a more common this binding problem, is called implicit loss of this .

The problem of implicit loss is often encountered in callback functions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function f1() {
    console.log(this.a);
}

function f2(f) {
    // f引用的是f1
    f();
}

var obj = {
    a: "obj",
    f: f1
};

var a = "global"

f2(obj.f); // 输出:"global"

When executing f2(obj.f), you actually try to assign f1 to the argument f of f2, which is actually just a reference to the function, so the result is "global".

Show bindings

The bind this value can be displayed with the call, apply and bind functions.

1
2
3
4
5
6
7
8
function f() {
    console.log(this.a);
}

var obj = {
    a: 2
};
f.call(obj); // 输出:2

With the call method, the display specifies that this is bound to the obj object, and the output is 2. Multiple arguments can be passed to call, with the first argument specifying the this binding object and the subsequent arguments specifying the arguments needed to execute the function.

The apply method is similar to call, except that the call method takes a list of arguments, while the apply method takes an array of multiple arguments.

The bind method creates a new function, and when bind is called, this of this new function is specified as the first argument of bind, and the rest of the arguments will be used as arguments of the new function for the call.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
const module = {
    x: 42,
    getX: function() {
        return this.x;
    }
};

const unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// expected output: undefined

const boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
// expected output: 42

new binding

If you haven’t worked in a language other than JavaScript, you’ll probably rarely use the new keyword, because there are no actual classes in JavaScript (classes in Es6 are actually functions at the bottom). newoperator, they do not belong to a class, nor do they instantiate a class, they are just ordinary functions that are called by thenew` operator.

Calling a function (constructor) with new will automatically perform the following steps.

  1. Create a new empty object as an instance of the object to be returned.
  2. Prototype the empty object to the function’s prototype property.
  3. Bind the empty object to the this keyword inside the function.
  4. Execute the code inside the function (usually by assigning a value to the this attribute and initializing the object).

The third step above, is the new binding.

Complete steps

After describing the four binding rules, the next step is to describe the complete steps to determine the priority of each method.

  1. new binding, if the function is called in new, then this is bound to the newly created object.
  2. display binding, if the function is called in call, apply or bind, then this binds to the specified object.
  3. implicit binding, if the function is called in some context object, if yes, then this is bound to that context object. 4.
  4. If none of the above, the default binding is used, which is undefined in strict mode and the global object in non-strict mode.

Exceptions

There are always exceptions to everything, and here is one of the more common exceptions - the arrow function =>.

Instead of using the four binding rules of this, arrow functions determine who to bind this to based on the outer (function or global) scope.

1
2
3
4
5
6
7
const myObject = {
    myMethod: () => {
        console.log(this);
    }
}

myObject.myMethod(); // this === global object

Why is the this of the above myMethod() execution a global object? Because the this of an arrow function inherits from its outer (function or global) scope.

Continue with an example.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const myObject = {
    myArrowFunction: null,
    myMethod: function() {
        this.myArrowFunction = () => {console.log(this)};
    }
};

myObject.myMethod(); // this === myObject

myObject.myArrowFunctoin(); // this === myObject

const myArrowFunction = myObject.myArrowFunction;
myArrowFunction(); // this === myObject