Tips for using this keyword in JavaScript how it works and considerations

  • 2020-03-30 03:02:00
  • OfStack

To understand this according to its position, there are roughly three kinds of situations:

In a function: this is usually an implicit argument.

Out of function (top-level scope) : in the browser this refers to the global object; In node. js, the name refers to the exports of a module.

3. String passed to eval() : if eval() is called directly, this refers to the current object. If eval() is called indirectly, this refers to the global object.

For these categories, we made corresponding tests:
1. This in the function

Functions can basically represent all callable structures in JS, so this is the most common scenario to use this, and functions can be divided into the following three roles:

      Real function
      The constructor
      methods

1.1   This in the real function

In a real function, the value of this depends on the pattern of the context in which it resides.

Sloppy mode: this refers to the global object (which in the browser is the window).


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

Strict mode: this is undefined.


function strictFunc() {
    'use strict';
    console.log(this === undefined); // true
}
strictFunc();

This is an implicit argument to the function, so its value is always the same. However, you can explicitly define the value of this by using the method call() or apply().


function func(arg1, arg2) {
    console.log(this); // 1
    console.log(arg1); // 2
    console.log(arg2); // 3
}
func.call(1, 2, 3); // (this, arg1, arg2)
func.apply(1, [2, 3]); // (this, arrayWithArgs)

1.2   This in the constructor

You can use a function as a constructor with new. The new operation creates a new object and passes the object into the constructor through this.


var savedThis;
function Constr() {
    savedThis = this;
}
var inst = new Constr();
console.log(savedThis === inst); // true

The implementation principle of the new operation in JS is roughly as shown in the following code (see here for a more accurate implementation, which is also a bit more complicated) :


function newOperator(Constr, arrayWithArgs) {
    var thisValue = Object.create(Constr.prototype);
    Constr.apply(thisValue, arrayWithArgs);
    return thisValue;
}

1.3   Method this

The use of this in methods tends to be more traditional in object-oriented languages: this points to the receiver, that is, the object containing the method.


var obj = {
    method: function () {
        console.log(this === obj); // true
    }
}
obj.method();

2. This in scope

In the browser, the scope is the global scope, and this refers to the global object (like the window) :


<script>
    console.log(this === window); // true
</script>

In node. js, you usually execute functions in a module. Therefore, the top-level scope is a special module scope:


// `global` (not `window`) refers to global object:
console.log(Math === global.Math); // true

// `this` doesn't refer to the global object:
console.log(this !== global); // true
// `this` refers to a module's exports:
console.log(this === module.exports); // true

3. This in eval()

Eval () can be called either directly (by calling the function name 'eval') or indirectly (by calling something else, such as call()). For more details, see here.


// Real functions
function sloppyFunc() {
    console.log(eval('this') === window); // true
}
sloppyFunc();

function strictFunc() {
    'use strict';
    console.log(eval('this') === undefined); // true
}
strictFunc();

// Constructors
var savedThis;
function Constr() {
    savedThis = eval('this');
}
var inst = new Constr();
console.log(savedThis === inst); // true

// Methods
var obj = {
    method: function () {
        console.log(eval('this') === obj); // true
    }
}
obj.method();

4. The trap associated with this

You need to be careful of the following three pitfalls related to this. Note that in the following examples, Strict mode improves the security of your code. Since the value of this is undefined in the real function, you get a warning when something goes wrong.

4.1   Forget to use new

If you're not calling the constructor with new, you're using a real function. So this will not be the value you expect. In Sloppy mode, this points to the window and you're going to create a global variable:


function Point(x, y) {
    this.x = x;
    this.y = y;
}
var p = Point(7, 5); // we forgot new!
console.log(p === undefined); // true

// Global variables have been created:
console.log(x); // 7
console.log(y); // 5

However, if you use strict mode, you will still get a warning (this===undefined) :


function Point(x, y) {
    'use strict';
    this.x = x;
    this.y = y;
}
var p = Point(7, 5);
// TypeError: Cannot set property 'x' of undefined

4.2 improper use of methods

If you take the value of a method directly (instead of calling it), you are using the method as a function. When you pass a method as an argument into a function or a calling method, you probably do so. This is the case with setTimeout() and the registration event handlers (event handlers). I'll use the callIt() method to simulate this scenario:



function callIt(func) {
    func();
}

If you're calling a method as a function in Sloppy mode, *this* points to a global object, so global variables are always created.


var counter = {
    count: 0,
    // Sloppy-mode method
    inc: function () {
        this.count++;
    }
}
callIt(counter.inc);

// Didn't work:
console.log(counter.count); // 0

// Instead, a global variable has been created
// (NaN is result of applying ++ to undefined):
console.log(count);  // NaN

If you do this in Strict mode, this is undefined, you still won't get what you want, but at least you'll get a warning:


var counter = {
    count: 0,
    // Strict-mode method
    inc: function () {
        'use strict';
        this.count++;
    }
}
callIt(counter.inc);

// TypeError: Cannot read property 'count' of undefined
console.log(counter.count);

To get the desired results, use bind() :


var counter = {
    count: 0,
    inc: function () {
        this.count++;
    }
}
callIt(counter.inc.bind(counter));
// It worked!
console.log(counter.count); // 1

Bind () also creates a function that always sets the value of this to counter.

4.3 to hide this

When you use a function in a method, you often forget that the function has its own this. This is different from the method, so you can't mix the two this's. See the following code for details:


var obj = {
    name: 'Jane',
    friends: [ 'Tarzan', 'Cheeta' ],
    loop: function () {
        'use strict';
        this.friends.forEach(
            function (friend) {
                console.log(this.name+' knows '+friend);
            }
        );
    }
};
obj.loop();
// TypeError: Cannot read property 'name' of undefined

In the above example, the this.name in the function cannot be used, because the value of this in the function is undefined, which is not the same as the value of this in the method loop(). Here are three ways to solve this problem:

1, that=this, assign this to a variable, so that this is explicitly represented (besides that, self is a very common variable name for this), and then use that variable:


loop: function () {
    'use strict';
    var that = this;
    this.friends.forEach(function (friend) {
        console.log(that.name+' knows '+friend);
    });
}

2, the bind (). Use bind() to create a function whose this always holds the value you want to pass (in this case, the method's this) :


loop: function () {
    'use strict';
    this.friends.forEach(function (friend) {
        console.log(this.name+' knows '+friend);
    }.bind(this));
}

3. Use the second parameter of forEach. The second argument to forEach is passed into the callback function and used as this.


loop: function () {
    'use strict';
    this.friends.forEach(function (friend) {
        console.log(this.name+' knows '+friend);
    }, this);
}

5. Best practices

Theoretically, I believe that real functions do not have their own this, and the above solutions are also based on this idea. ECMAScript 6 USES the arrow function to achieve this effect, which is a function that does not have its own this. In a function like this you can just use this and not worry about whether it's implicit or not.


loop: function () {
    'use strict';
    // The parameter of forEach() is an arrow function
    this.friends.forEach(friend => {
        // `this` is loop's `this`
        console.log(this.name+' knows '+friend);
    });
}

I don't like the way some apis treat this as an additional argument to a real function:


beforeEach(function () {  
    this.addMatchers({  
        toBeInRange: function (start, end) {  
            ...
        }  
    });  
});

Passing in an implicit parameter in an explicit way makes the code more understandable, and it's consistent with the arrow function:


beforeEach(api => {
    api.addMatchers({
        toBeInRange(start, end) {
            ...
        }
    });
});


Related articles: