不管在 javascript
的平常使用中仍是前端面试过程当中,this
的出镜率都极高。这无疑说明了,this
的重要性。可是 this
很是灵活,致使不少人以为 this
的行为难以理解。本文从为何要有 this
做为切入点,总结了 this
的六大规则,但愿能帮助你解答困惑。javascript
this 实际上至关于一个参数,这个参数多是开发中手动传入的,也多是 JS 或者第三方传入的。
这个参数,一般指向的是函数执行时的“拥有者”。this
的机制,可让函数设计的更加简洁,而且复用性更好。html
this
是在函数执行时进行绑定的,绑定规则一共六条,分别是:前端
new
绑定:使用 new
关键字建立对象时,this
会绑定到建立的对象上。java
显式绑定:使用 call
、apply
或 bind
方法显式绑定时, this
为其第一个参数。node
隐式绑定:当函数挂在对象上执行时,系统会隐式地将 this
绑定到该对象上。git
默认绑定:当函数独立执行时,严格模式 this
的默认绑定值为 undefined
,不然为全局对象。github
箭头函数绑定:使用箭头函数时,this
的绑定值等于其外层的普通函数(或者全局对象自己)的this
。面试
系统或第三方绑定:当函数做为参数,传入系统或者第三方提供的接口时,传入函数中的 this
是由系统或者第三方绑定的。数组
this
的做用this 的机制提供了一个优雅的方式,隐式地传递一个对象,这可让函数设计的更加简洁,而且复用性更好。浏览器
考虑下面一个例子,有两个按钮,点击后将其背景改成红色。
function changeBackgroundColor(ele) { ele.style.backgroundColor = 'red'; } btn1.addEventListener('click',function () { changeBackgroundColor(btn1); }); btn2.addEventListener('click',function () { changeBackgroundColor(btn2); });
在这里,咱们显式地将被点击的元素传递给了 changeBackgroundColor
函数。但实际上,这里能够利用 this
隐式传递上下文的特色,直接在函数获取当前被点击的元素。以下:
function changeBackgroundColor() { this.style.backgroundColor = 'red'; } btn1.addEventListener('click',changeBackgroundColor); btn2.addEventListener('click',changeBackgroundColor);
在第一个例子中,被点击元素是经过 ele
,这个形式参数来代替的。而在第二个例子中,是经过一个特殊的关键字 this
来代替。this
它的做用和形式参数相似,其本质上是一个对象的引用,它的特殊性在于不须要手动传值,因此使用起来会更加简单和方便。
在实际使用中, this
究竟指向哪一个对象是最使人困惑的。本文归类了六类情景,总结六条 this
的绑定规则。
new
绑定使用 new
建立对象的时候,类中的 this
指的是什么?
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
在上面例子中,使用了 ES6 的语法建立了 Person
类。在使用 new
关键字建立对象的过程当中,this
会由系统自动绑定到建立的对象上,也就是 xiaoMing
。
规则一:在使用 new
关键字建立对象时,this
会绑定到建立的对象上。
情景二,使用 call
、apply
和 bind
方法,显式绑定 this
参数。
以 call
为例,call
方法的第一个传入的参数,是 this
引用的对象。
function foo() { console.log( this === obj ); // true console.log( this.a === 2 ); // true } const obj = { a: 2 }; foo.call( obj );
在显式传递的状况下,this
指向的对象很明显,就是 call
、apply
或 bind
方法的第一个参数。
规则二:使用 call
、apply
或 bind
方法显式绑定时, this
为其第一个参数。
隐式绑定和显式绑定不一样的地方在于,显式绑定由开发者来指定 this
;而隐式绑定时,函数或方法都会有一个“拥有者”,这个“拥有者”指的是直接调用的函数或方法对象。
先看一个最简单的例子。
function bar() { console.log( this === obj ); } const obj = { foo: function () { console.log( this === obj ); }, bar: bar }; obj.foo(); // true obj.bar(); // true
函数 foo
是直接挂在对象 obj
里面的,函数 bar
是在外面定义的,而后挂在对象 obj
上的。不管函数是在何处定义,但最后函数调用时,它的“拥有者”是 obj
。因此 this
指向的是函数调用时的“拥有者” obj
。
为了更加深刻的理解,再考虑函数从新赋值到新的对象上的状况,来看看下面的例子。
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();
在该例子中,将 obj1
中的 foo
和 bar
方法赋值给了 obj2
。函数调用时,“拥有者”是 obj2
,而不是 obj1
。因此 this
指向的是 obj2
。
对象能够多层嵌套,在这种状况下执行函数,函数的“拥有者”是谁呢?
const obj1 = { obj2: { foo: function foo() { console.log( this === obj1 ); // false console.log( this === obj1.obj2 ); // true } } }; obj1.obj2.foo()
foo
方法/函数中的直接调用者是 obj2
,而不是 obj1
,因此函数的“拥有者”指向的是离它最近的直接调用者。
若是一个方法/函数,在它的直接对象上调用执行,又同时执行了 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
绑定为函数执行时的“拥有者”。
前一小段,讨论了函数做为对象的方法执行时的状况。本小段,要讨论的是,函数独立执行的状况。
在函数直接调用的状况下,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
。
箭头函数实际上,只是一个语法糖,实际上箭头函数中的 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
绑定值相同。
在 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
是由系统或者第三方绑定的。
参考文章:
查完规范后,用伪代码再总结一下。
规范地址:
Construct:http://www.ecma-international...
Function Objects:http://www.ecma-international...
Function Calls:http://www.ecma-international...
ArrowFunction:http://www.ecma-international...
伪代码
if (`newObj = new Object()`) { this = newObj } else if (`bind/call/apply(thisArgument,...)`) { if (`use strict`) { this = thisArgument } else { if (thisArgument == null || thisArgument == undefined) { this = window || global } else { this = ToObject(thisArgument) } } } else if (`Function Call`) { if (`obj.foo()`) { // base value . Reference = base value + reference name + strict reference // 例外: super.render(obj). this = childObj ? this = obj } else if (`foo()`) { // 例外: with statement. this = with object this = `use strict` ? undefined : window || global } }