指向调用者
一般情况下,this指代的对象都是调用的对象,即谁调用,则this指向谁;我们来看如下示例。
作为函数调用
JavaScript中,作为函数调用的意思就是,调用对象是全局对象。
1 | // 浏览器环境下 |
1 | // node环境下 |
在上面的代码中,由于是全局对象调用的foo函数,因此this就指向了全局对象。在浏览器环境中,全局对象即window对象,而在node环境下,全局对象是global对象,因此这里需要注意。
也因此,如setTimeout和setInterval这种本身就属于浏览器全局对象的函数,不管在哪个对象中调用,this都是指向window(除非使用call,apply和bind,后面会讲到)。我们可以理解为实际调用是前面加了window对象。
1 | // setTimeout(foo, time) 等同于 window.setTimeout(foo, time) |
指向自定义的当前对象
1 | let foo = { |
当一个方法具体从属于一个对象的时候,this就变成了该对象,如上述例子所示。我们再来看一个这种情况的一个经典应用例子。
1 | function User(name) { |
在之前的文章我们讲过JavaScript中的OOP的实现是基于原型的,当我们需要为一个类添加成员方法的时候就需要在该构造器函数的prototype对象中添加方法,而且可以在其中直接使用this关键字代表当前调用对象。实际上由于JavaScript的原型链逐级往上寻找属性,我们最后调用的成员方法都是在上级(或者更上级)的prototype中定义的,所有对象共享同一套方法,也正是因为this指代当前对象,才能使这种面向对象编程的设计能够实现。
嵌套函数内的this
如果嵌套函数作为方法调用,其this的值指向调用它的对象。如果嵌套函数作为函数调用,其this值不是全局对象不是全局对象(非严格模式下)就是undefined(严格模式下)。很多人误以为调用嵌套函数时this会指向调用外层函数的上下文。(参考2)
1 | ; |
严格模式和非严格模式
严格模式下如果不手动绑定this(使用call(),apply(),或者bind()),则this的值一定是undefined。
1 | (function() { |
非严格模式下非嵌套函数,且不手动绑定this,则this的值为全局对象。
1 | (function() { |
ES6中的箭头函数
之前写ES6中的箭头函数时,有讲过箭头函数需要特别注意this的作用域。
1 | function foo() { |
上面的示例代码中,箭头函数bar内的this和箭头函数bar外的this指向的是同一个对象,都是foo的当前调用对象(具体到上面的例子中的this,仔细分析一下的话应该都是全局对象)。
call,apply和bind
由于this指向的对象总是不确定的,所以有时候我们需要为一个函数绑定一个this,这个时候我们就需要考虑使用call,apply和bind了。
1 | const o = { |
1 | const o = { |
上例中,我们为传入setTimeout的第一个参数(函数)绑定了对象o,作为该函数的this,也就是说该函数的this永远指向o,而不是默认的当前对象。
apply和call与bind作用相似,不过bind是永久性的,调用以后返回的就是this经过指定后的原函数,而apply和call是立刻执行。
1 | // 默认情况下 |
1 | // 使用call的情况 |
1 | // 使用apply的情况 |
由上我们可以看出,apply和call这两个函数与bind的区别在于bind绑定永久,而apply和call立刻执行。此外,使用apply调用函数的时候参数放在第二个参数的位置,作为数组传入;使用call调用函数的时候参数放在第二个参数以及之后的位置传入。
构造器函数中的this(使用new关键字)
JavaScript中如果使用new关键字,一个函数还可以作为构造器(constructor)构建一个新的对象。
1 | function Person(name, age) { |
使用new关键字创建对象时,构造器中的this都会被指向当前新创建的对象(注意构造器不需要返回任何显式创建的对象)。新对象的__proto__属性将会指向Person.prototype属性。
DOM事件处理函数
在DOM事件处理中,this指向当前事件的发生对象。
1 | const btn = document.getElementsByTagName('button')[0]; |
上例中,点击页面的第一个按钮将在控制台输出true
。