针对this的使用已经看过好多文章,但每次遇到this还是要想半天,今天尝试总结下我们可能遇到的所有情况,不一定完善,有问题请指出。
what’s this?
谈及 Javascript 中的 this,竟然让人觉得头疼,它不像 Java,C++ 中的 this 指向调用 this 的对象。
首先,this 是 JavaScript中的一个keyword(关键词),以下是MDN的解释。
A function’s this keyword behaves a little differently in JavaScript compared to other languages. It also has some differences between strict mode and non-strict mode.
与其他语言相比,函数的 this 关键字在 JavaScript 中的表现略有不同,此外,在严格模式和非严格模式之间也会有一些差别。
In most cases, the value of
thisis determined by how a function is called. It can’t be set by assignment during execution, and it may be different each time the function is called. ES5 introduced thebindmethod to set the value of a function’sthisregardless of how it’s called, and ES2015 introduced arrow functions which don’t provide their ownthisbinding (it retains thethisvalue of the enclosing lexical context).在绝大多数情况下,函数的调用方式决定了
this的值。this不能在执行期间被赋值,并且在每次函数被调用时this的值也可能会不同。ES5引入了bind方法来设置函数的this值,而不用考虑函数如何被调用的,ES2015 引入了支持this词法解析的箭头函数(它在闭合的执行上下文内设置this的值)。
理解上面这段话,我们来看两个例子,第一个例子中,同一个函数由于调用方式的不同,this指向了不一样的对象:
1 | var a = 10; |
除此之外,this 为保留字,你不能重写 this,也就是this一旦被确定,执行期间就不可更改了:
1 | var a = 10; |
接下里我们要牢记:在函数中 this 到底取何值,是在函数真正被调用执行的时候确定下来的,函数定义的时候确定不了。 也就是执行上下文被创建时确定的。因此,一个函数中的this指向,可以是非常灵活的,接下来来看我们可能会遇到的情况。
全局中的this
在全局环境中,this 永远指向全局对象(宿主环境为web时即 window,在nodejs中为 global),不需过多考虑。
1 | console.log(this === window); //true |
普通函数中的this
首先我们复习一下刚才的话,在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。
其次我们直接抛出结论:如果调用者函数,被某一个对象所拥有,那么该函数在调用时,内部的this指向该对象。如果函数独立调用,那么该函数内部的this,则指向undefined(但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象,也就是window),从结论中我们可以看出,想要准确确定this指向,找到函数的调用者以及区分他是否是独立调用就变得十分关键。
1 | // 为了能够准确判断,我们在函数内部使用严格模式,因为非严格模式会自动指向全局 |
在上面的简单例子中,fn()作为独立调用者,按照定义的理解,它内部的 this 指向就为 undefined。而window.fn()则因为fn被window所拥有,内部的this就指向了window对象。
掌握了这个规则大部分问题都可以解决。
构造函数中的this
所谓的构造函数就是一种特殊的方法,主要用来在创建对象时初始化对象,一般构造函数的函数名首字母大写,例如像 Object,Function,Array 这些都属于构造函数。
1 | function Person(name, age) { |
我们已经知道,this,是在函数调用过程中确定,因此,搞明白new的过程中到底发生了什么就变得十分重要。
通过new操作符调用构造函数,会经历以下4个阶段:
- 创建一个新的对象;
- 将构造函数的this指向这个新对象;
- 指向构造函数的代码,为这个对象添加属性,方法等;
- 返回新对象
因此,当new操作符调用构造函数时,this其实指向的是这个新创建的对象,最后又将新的对象返回出来,被实例对象p1接收。因此,我们可以说,这个时候,构造函数的this,指向了新的实例对象,p1。
原型上函数的this
原型上函数的this,其实没有什么特殊之处。在下面的例子中,借用我们上面的规则,foo.getX()中getX为调用者,他被foo所有,因此getX中的 this 指向了foo对象。
1 | function Foo(){ |
箭头函数中的this
箭头函数本身没有this,需要通过查找作用域链来确定 this 的值。
这就意味着如果箭头函数被非箭头函数包含,this 绑定的就是最近一层非箭头函数的 this。
1 | var obj = { |
所以,用 call() 或者 apply() 调用箭头函数时,无法对 this 进行绑定,即传入的第一个参数被忽略。
如果使用箭头函数,以前的这种 hack 写法:var self = this ,就不再需要了。
使用call、apply或bind显示指定this
1 | var obj = { |
JavaScript内部提供了一种机制,让我们可以自行手动设置this的指向。
call()、apply()可以改变函数运行时的执行环境,foo.call()、foo.apply()这样的语句可以看作执行foo(),只不过foo()中的this指向了后面的第一个参数。
foo.bind({a:1})却并不如此,执行该条语句仅仅得到了一个新的函数,新函数的this被绑定到了后面的第一个参数,亦即新的函数并没有执行。