Whether in the daily use of javascript or in the front-end interview process, this has a very high appearance rate. This undoubtedly illustrates the importance of this. But this is very flexible, which makes many people find the behavior of this difficult to understand. This article starts with why this is necessary and summarizes the six rules of this. I hope it can help you answer your confusion.
Introduction
This is actually equivalent to a parameter. This parameter may be passed in manually during development, or it may be JS or the third Incoming from three parties.
This parameter usually points to the "owner" when the function is executed. The this mechanism can make function design more concise and more reusable.
this is bound when the function is executed. There are six binding rules in total, which are:
●new binding: When using the new keyword to create an object, this will be bound to on the created object.
●Explicit binding: When explicitly bound using the call, apply or bind method, this is its first parameter.
●Implicit binding: When a function is executed on an object, the system will implicitly bind this to the object.
●Default binding: When the function is executed independently, the default binding value of this in strict mode is undefined, otherwise it is the global object.
●Arrow function binding: When using arrow functions, the binding value of this is equal to the this of its outer ordinary function (or the global object itself).
●System or third-party binding: When a function is passed as a parameter to an interface provided by the system or a third party, this in the passed function is bound by the system or the third party.
The role of this
This mechanism provides an elegant way to implicitly pass an object, which allows function design More concise and better reusable.
Consider the following example, there are two buttons that change their background to red when clicked.
function changeBackgroundColor(ele) { ele.style.backgroundColor = 'red'; } btn1.addEventListener('click',function () { changeBackgroundColor(btn1); }); btn2.addEventListener('click',function () { changeBackgroundColor(btn2); });
Here, we explicitly pass the clicked element to the changeBackgroundColor function. But in fact, you can take advantage of this feature of implicitly passing context to get the currently clicked element directly in the function. As follows:
function changeBackgroundColor() { this.style.backgroundColor = 'red'; } btn1.addEventListener('click',changeBackgroundColor); btn2.addEventListener('click',changeBackgroundColor);
In the first example, the clicked element is replaced by ele, this formal parameter. In the second example, it is replaced by a special keyword this. This has a similar function to formal parameters. It is essentially a reference to an object. Its special feature is that it does not require manual value transfer, so it is simpler and more convenient to use.
Six Rules
In actual use, which object this points to is the most confusing. This article classifies six types of scenarios and summarizes six binding rules for this.
1.new binding
When using new to create an object, this in the class refers to What is?
class Person { constructor(name){ this.name = name; } getThis(){ return this } } const xiaoMing = new Person("小明"); console.log(xiaoMing.getThis() === xiaoMing); // true console.log(xiaoMing.getThis() === Person); // false console.log(xiaoMing.name === "小明"); // true
In the above example, the Person class was created using ES6 syntax. In the process of creating an object using the new keyword, this will be automatically bound to the created object by the system, that is, xiaoMing.
Rule 1: When using the new keyword to create an object, this will be bound to the created object.
2. Explicit binding
Scenario 2, use call, apply and bind methods to explicitly bind this parameter .
Take call as an example. The first parameter passed in to the call method is the object referenced by this.
function foo() { console.log( this === obj ); // true console.log( this.a === 2 ); // true}const obj = { a: 2}; foo.call( obj );
In the case of explicit passing, the object pointed to by this is obvious, which is the first parameter of the call, apply or bind method.
Rule 2: When explicitly bound using the call, apply or bind method, this is its first parameter.
3. Implicit binding
The difference between implicit binding and explicit binding is that explicit binding It is up to the developer to specify this; when implicitly bound, the function or method will have an "owner", and this "owner" refers to the function or method object that is directly called.
Example 1
Let’s look at the simplest example first.
function bar() { console.log( this === obj ); }const obj = { foo: function () { console.log( this === obj ); }, bar: bar }; obj.foo(); // trueobj.bar(); // true
The function foo is directly hung in the object obj, and the function bar is defined outside and then hung on the object obj. No matter where the function is defined, when the function is finally called, its "owner" is obj. So this points to the "owner" obj when the function is called.
Example 2
In order to have a deeper understanding and consider the situation of reassigning a function to a new object, let’s take a look at the following example.
function bar() { console.log( this === obj1 ); // false console.log( this === obj2 ); // true}const obj1 = { foo: function () { console.log( this === obj1 ); // false console.log( this === obj2 ); // true }, bar: bar };const obj2 = { foo: obj1.foo, bar: obj1.bar }; obj2.foo(); obj2.bar();
In this example, the foo and bar methods in obj1 are assigned to obj2. When the function is called, the "owner" is obj2, not obj1. So this points to obj2.
Example 3
Objects can be nested at multiple levels. In this case, when a function is executed, who is the "owner" of the function?
const obj1 = { obj2: { foo: function foo() { console.log( this === obj1 ); // false console.log( this === obj1.obj2 ); // true } } }; obj1.obj2.foo()
foo The direct caller in the method/function is obj2, not obj1, so the "owner" of the function points to the nearest direct caller.
Example 4
如果一个方法/函数,在它的直接对象上调用执行,又同时执行了 call 方法,那么它是属于隐式绑定还是显式绑定呢?
const obj1 = { a: 1, foo: function () { console.log(this === obj1); // false console.log(this === obj2); // true console.log(this.a === 2); // true } };const obj2 = { a: 2}; obj1.foo.call(obj2); // true
由上,可以看出,如果显式绑定存在,它就不可能属于隐式绑定。
规则三:如果函数是挂在对象上执行的,这个时候系统会隐式的将 this 绑定为函数执行时的“拥有者”。
4.默认绑定
前一小段,讨论了函数作为对象的方法执行时的情况。本小段,要讨论的是,函数独立执行的情况。
在函数直接调用的情况下,this 绑定的行为,称之为默认绑定。
例一
为了简单起见,先讨论在浏览器的非严格模式的下绑定行为。
function foo() { console.log( this === window); // true} foo();
在上面的例子中,系统将 window 默认地绑定到函数的 this 上。
例二
在这里,先介绍一种我们可能会在代码中见到的显式绑定 null 的写法。
function foo() { console.log( this == window ); // true} foo.apply(null);
将例一默认绑定的情况,改为了显式绑定 null 的情况。
在实际开发中,我们可能会用到 apply 方法,并在第一个参数传入 null 值,第二个参数传入数组的方式来传递数组类型的参数。这是一种传统的写法,当然现在可以用 ES6 的写法来代替,但是这不在本文的讨论范围内。
在本例最需要关注的是,this 竟然指向的 window 而不是 null。个人测试的结果是,在函数独立调用时,或者显式调用,传入的值为 null 和 undefined 的情况下,会将 window 默认绑定到 this 上。
在函数多次调用,形成了一个调用栈的情况下,默认绑定的规则也是成立的。
例三
接着,探讨下严格模式下,this 的默认绑定的值。
"use strict"; function foo() { console.log( this === undefined ); } foo(); // true foo.call(undefined); // true foo.call(null); // false
在严格模式下,this 的默认绑定的值为 undefined。
规则四:在函数独立执行的情况下,严格模式 this 的默认绑定值为 undefined,否则默认绑定的值为 window。
5.箭头函数绑定
箭头函数实际上,只是一个语法糖,实际上箭头函数中的 this 实际上是其外层函数(或者 window/global 本身)中的 this。
// ES6 function foo() { setTimeout(() => { console.log(this === obj); // true }, 100); } const obj = { a : 1 } foo.call(obj); // ES5 function foo() { var _this = this; setTimeout(function () { console.log(_this === obj); // true }, 100); } var obj = { a : 1 } foo.call(obj);
规则五:使用箭头函数时,this 的绑定值和其外层的普通函数(或者 window/global 本身) this 绑定值相同。
6.系统或第三方绑定
在 JavaScript 中,函数是第一公民,可以将函数以值的方式,传入任何系统或者第三方提供的函数中。现在讨论,最后一种情况。当将函数作为值,传入系统函数或者第三方函数中时,this 究竟是如何绑定的。
我们在文章一开始提到的,两个按钮例子,系统自动将 this 绑定为点击的按钮。
function changeBackgroundColor() { console.log(this === btn1); // true} btn1.addEventListener('click',changeBackgroundColor);
接着测试系统提供的 setTimeout 接口在浏览器和 node 中绑定行为。
// 浏览器 setTimeout(function () { console.log(this === window); // true },0) // node setTimeout(function () { console.log(this === global); // false console.log(this); // Timeout },0)
很神奇的是,setTimeout 在 node 和浏览器中的绑定行为不一致。如果我们将 node 的中的 this 打印出来,会发现它绑定是一个 Timeout 对象。
如果是第三发提供的接口,情况会更加复杂。因为在其内部,会将什么值绑定到传入的函数的 this 上,事先是不知道的,除非查看文档或者源码。
系统或者第三方,在其内部,可能会使用前面的五种规则一种或多种规则,对传入函数的 this 进行绑定。所以,规则六,实际上一条在由前五条规则上衍生出来的规则。
规则六:调用系统或者第三方提供的接口时,传入函数中的 this 是由系统或者第三方绑定的。