在编写javascript代码的时候,我们经常需要使用“this”关键字。“this”关键字在不同的上下文中所代表的对象是不同的。在这篇文章中,我们将详细的讲解“this”关键字在不同上下文中的含义和一些常见问题及其解决方案。
常见问题
在获取方法中使用this
关键字
使用this
关键字最常见的错误是将一个对象的方法分配给一个变量,并希望this
关键字指向原来的对象。来看下面的例子:
var car = { brand: "Nissan", getBrand: function(){ console.log(this.brand); } }; var getCarBrand = car.getBrand; getCarBrand(); // 输出: undefined
上面的代码使用getCarBrand
变量来接收car.getBrand()
的引用,实际上得到的只是另一个指向getBrand()
本身的引用。我们指定函数的调用者决定上下文,这里的调用者是getCarBrand()
,这是一个简单的函数调用。
要证明getCarBrand
变量并不指向任何的函数,可以简单的使用alert(getCarBrand);
,你在控制台中可以看到输出的是:
function(){ console.log(this.brand); }
getCarBrand
保存的只是一个普通的函数,而不是car
对象的一个函数。因此,this.brand
会被翻译为window.brand
。所以得到的结果是undefined
。
那么我们要如何解决这个问题呢?如果我们需要this
指向原来的对象,我们需要明确的使用bind()方法将getBrand()
绑定到car
对象上。然后在将它赋值给getCarBrand
变量。
var getCarBrand = car.getBrand.bind(car); getCarBrand(); // 输出: Nissan
通过上面的方法,我们可以得到正确的结果,因为我们重新定义了我们需要的上下文。
在回调函数中使用this
关键字
另一个容易出错的地方是我们将一个带有this
关键字的方法作为回调函数来使用。例如:
<button id="btn" type="button">Get the car's brand</button> var car = { brand: "Nissan", getBrand: function(){ console.log(this.brand); } }; var el = document.getElementById("btn"); el.addEventListener("click", car.getBrand); //输出:undefined
虽然我们使用的是car.getBrand
,事实上我们得到的egetBrand()
函数是附加在button
对象上的方法。
这里发生的事情和上面的例子相似,不同的是现在car.getBrand
不是被明确的赋值,而是隐含的赋值。所得到的结果是一样的,一个绑定到button
上的普通函数。
换句话来讲,我们在一个对象上执行一个方法,与这个对象原理定义的方法是不一样的。this
关键字不在指向对象本身,而是指向调用对象的那个方法。
对于上面的例子来说,我们在按钮上执行car.getBrand
方法,而不是在car
对象上。因此,this
关键字指向的是el
(按钮元素),而不是car
对象。
如果希望this
关键字指向原来的对象,和上面一样,我们需要明确的使用bind()
方法在car
对象上绑定getBrand()
方法。
el.addEventListener("click", car.getBrand.bind(car));
在闭包中使用this
关键字
在闭包中使用this
关键字也是容易发生错误的地方。看下面的例子:
var car = { brand: "Nissan", getBrand: function(){ var closure = function(){ console.log(this.brand); }; return closure(); } }; car.getBrand(); // 输出: undefined
这里得到的结果是“undefined”,因为闭包(内部函数)不接收外部函数的this
变量。最终的结果是this.brand
等于window.brand
,因为在内部函数中,this
关键字是被绑定到全局对象上的。
要修正这个问题,我们需要将this
关键字绑定到getBrand()
方法上。
var car = { brand: "Nissan", getBrand: function(){ var closure = function(){ console.log(this.brand); }.bind(this); return closure(); } }; car.getBrand(); // 输出: Nissan
这个绑定相当于car.getBrand.bind(car)
。
另一个修正闭包的方法是将this
值保存在一个变量中,这样可以避免不必要的变化。
var car = { brand: "Nissan", getBrand: function(){ var self = this; var closure = function(){ console.log(self.brand); }; return closure(); } }; car.getBrand(); // 输出: Nissan
ECMAScript 6中的解决方案
在上面的例子中我们看到了使用this
关键字应该注意的地方。在ECMAScript 6中我们可以类似上面那样使用this
关键字,但是ECMAScript 6为我们提供了箭头操作符,使代码可以更加优雅和清晰。
箭头操作符不是由function
关键字创建的,而是所谓的“fat arrow”操作符(=>
)。和常规的函数不同,箭头操作符从它们的封闭作用域中获取this
值。箭头绑定的函数不能够被重写,即使是通过new
操作符。
下面来看看箭头操作符是如何替代var self = this;
声明的。
var car = { brand: "Nissan", getBrand: function(){ // the arrow function keeps the scope of "this" lexical var closure = () => { console.log(this.brand); }; return closure(); } }; car.getBrand(); // 输出: Nissan
关于this
需要记住的东西
当你遇到this
关键字的时候,和js的其它功能模块一样,如果你了解它的底层原理和各种情况的处理方法,那么你就会更加有信心去理解和使用它。下面吗让我们来简单总结一下使用this
关键字需要注意的地方。
this
关键字在下面的情况下总是指向全局对象:- 在最外层的上下文中,在任何函数块之外
- 在函数中,但不是任何对象的方法
- 在函数中,但不是对象构造函数
- 当一个函数被作为父元素对象一个属性调用的时候,
this
指向父元素。 - 当使用
call()
或apply()
或bind()
方法来调用函数的时候,this
指向这些函数的第一个参数。如果第一个参数是null
或不是一个对象,this
指向全局对象。 - 当使用
new
操作符去调用一个函数的时候,this
指向新创建的对象。 - 当使用箭头操作符(ECMAScript 6)的时候,
this
依赖于词法作用域并指向其父元素对象。
记住上面的简单规则,我们就可以知道this
究竟是指向什么对象。如果不是我们需要的结果,我们可以使用相应的方法来修正它。
小结
JavaScript的this
关键字是一个比较复杂的概念,但是只要你多想多练就一定能够掌握它。希望这篇文章对你掌握this
关键字有所帮助。