To understand this based on where this is located, the situation can be roughly divided into three types:
1. In the function: this is usually an implicit parameter.
2. Outside the function (in the top scope): in the browser, this refers to the global object; in Node.js refers to the exports of modules.
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.
We have conducted corresponding tests for these categories:
1. This in the function
Functions can basically represent all the invoked structures in JS, so this is also the most common scenario where this is used, and functions can be subdivided into the following three roles:
Real functions
Constructor
method
1.1 this in real functions
In real functions, the value of this is a pattern that depends on the context in which it is located.
Sloppy mode: this refers to a global object (window in the browser).
The code copy is as follows:
function sloppyFunc() {
console.log(this === window); // true
}
sloppyFunc();
Strict mode: The value of this is undefined.
The code copy is as follows:
function strictFunc() {
'use strict';
console.log(this === undefined); // true
}
strictFunc();
This is an implicit parameter of a function, so its value is always the same. However, you can define this value by using call() or apply() methods to display the value.
The code copy is as follows:
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 through new. The new operation creates a new object and passes this object into the constructor through this.
The code copy is as follows:
var saved This;
function Constr() {
savedThis = this;
}
var inst = new Constr();
console.log(savedThis === inst); // true
The implementation principle of new operation in JS is roughly shown in the following code (see here for a more accurate implementation, this implementation is also more complicated):
The code copy is as follows:
function newOperator(Constr, arrayWithArgs) {
var thisValue = Object.create(Constr.prototype);
Constr.apply(thisValue, arrayWithArgs);
return thisValue;
}
1.3 this in the method
In methods, the usage of this tends to be more in the traditional object-oriented language: the receiver pointed to by this, that is, the object containing this method.
The code copy is as follows:
var obj = {
method: function () {
console.log(this === obj); // true
}
}
obj.method();
2. This in scope
In the browser, scope is the global scope, and this refers to this global object (like window):
The code copy is as follows:
<script>
console.log(this === window); // true
</script>
In Node.js, you usually execute functions in modules. Therefore, the top-level scope is a very special module scope:
The code copy is as follows:
// `global` (not `window`) refer 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 directly (by calling the function name 'eval') or indirectly (by other means, such as call()). For more details, please see here.
The code copy is as follows:
// Real functions
function sloppyFunc() {
console.log(eval('this') === window); // true
}
sloppyFunc();
function strictFunc() {
'use strict';
console.log(eval('this') === undefined); // true
}
strictFunc();
// Constructors
var saved This;
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. Traps related to this
You should be careful about the 3 traps related to this that will be introduced below. Note that in the following example, using Strict mode can improve the security of the code. Since in real functions, the value of this is undefined, you will get a warning when something goes wrong.
4.1 Forgot to use new
If you are not using new to call the constructor, you are actually using a real function. So this won't be the value you expect. In Sloppy mode, this points to window and you will create global variables:
The code copy is as follows:
function Point(x, y) {
this.x = x;
this.y = y;
}
var p = Point(7, 5); // we forget new!
console.log(p === undefined); // true
// Global variables have been created:
console.log(x); // 7
console.log(y); // 5
However, if you are using strict mode, you will still get a warning (this===undefined):
The code copy is as follows:
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 Inappropriate use method
If you directly get the value of a method (not calling it), you are using this method as a function. When you want to pass a method as a parameter into a function or a calling method, you will most likely do this. This is the case with setTimeout() and registration event handlers. I will use the callIt() method to simulate this scenario:
The code copy is as follows:
/** Similar to setTimeout() and setImmediate() */
function callIt(func) {
func();
}
If you call a method as a function in Sloppy mode, *this* points to the global object, so the created subsequently will be global variables.
The code copy is as follows:
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 and you still won't get the desired result, but at least you will get a warning:
The code copy is as follows:
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 expected results, you can use bind():
The code copy is as follows:
var counter = {
count: 0,
inc: function () {
this.count++;
}
}
callIt(counter.inc.bind(counter));
// It worked!
console.log(counter.count); // 1
bind() creates another function that can always set this value to counter.
4.3 Hide this
When you use functions in methods, you often ignore that functions have their own this. This is different from the method, so you cannot mix these two this together. For details, please see the following code:
The code copy is as follows:
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
This.name in the function in the example above cannot be used because the value of this function is undefined, which is different from this in the method loop(). The following are three ideas to solve this problem:
1. that=this, assign this to a variable, so that this is explicitly manifested (except that, self is also a very common variable name used to store this), and then use that variable:
The code copy is as follows:
loop: function () {
'use strict';
var that = this;
this.friends.forEach(function (friend) {
console.log(that.name+' knowledge '+friend);
});
}
2. bind(). Use bind() to create a function. This function always contains the value you want to pass (in the following example, this method):
The code copy is as follows:
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 parameter of forEach will be passed into the callback function and used as this of the callback function.
The code copy is as follows:
loop: function () {
'use strict';
this.friends.forEach(function (friend) {
console.log(this.name+' knows '+friend);
}, this);
}
5. Best Practices
In theory, I think the real function does not belong to its own this, and the above solution is also based on this idea. ECMAScript 6 uses arrow function to achieve this effect, which is a function that does not have its own this. In such a function you can use this at will, without worrying about whether there is any implicit existence.
The code copy is as follows:
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 some APIs regard this as an additional parameter to a real function:
The code copy is as follows:
beforeEach(function () {
this.addMatchers({
toBeInRange: function (start, end) {
...
}
});
});
Writing an implicit parameter as explicitly passed in, the code will appear better to understand, and this is consistent with the requirements of the arrow function:
The code copy is as follows:
beforeEach(api => {
api.addMatchers({
toBeInRange(start, end) {
...
}
});
});