本内容来自《你不知道的JavaScript(上卷)》,做了简单的总结。
this关键字是javascript最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有的函数作用域中。但是即使是非常有经验的javascript开发者也很难说出他到底指向什么。本节将分三个部分讲解javascript中的this:
为什么要使用this
两种常见的对于this的误解
this到底是什么
一、为什么要使用this
在书中通过两段代码的对比来说明为什么要使用this。第一段代码如下:这段代码在不同的上下问对象(me和you)中重复使用函数identify()和speak(),不用针对不同的对象编写不同版本的函数。
function identify(){
return this.name.toUpperCase();
}
function speak(){
var greeting = "hello, I am " + identify.call(this);
console.log(greeting);
}
var me = {
name: "zhou"
}
var you = {
name: "reader"
}
identify.call(me); //ZHOU
identify.call(you); //READER
speak.call(me); // hello, I am ZHOU
speak.call(you); // hello, I am READER
如果不使用this, 这段代码该如何写呢?那就需要给identify()和speak()显示传入一个上下文对象
function identify(cxt){
return cxt.name.toUpperCase();
}
function speak(cxt){
var greeting = "hello, I am " + identify(cxt);
console.log(greeting);
}
identify(you); //READER
speak(me); //hello, I am ZHOU
对比发现:this提供了额一种更优雅的方式来隐式“传递”一个对象引用。因为,随着你使用的模式越来越复杂,显式传递上下文对象会让代码变得越来越混乱。因此,通过使用this可以将API设计的更加简洁并且易于复用。
二、两种常见的对于this的误解
误解1.指向函数自身
把this理解为指向函数自身,这个推断从英语语法角度是说的通的。
常见的在函数内部引用自身的情况有:递归或者是一个在第一次被调用后自己接触绑定的事件处理器。 JavaScript的新手开发者(比如说我)通常认为:既然可以把函数看作一个对象,那就可以在调用函数时存储状态(属性的值)。
现在我们来分析这个模式,让大家看到this并不像所想的那样指向函数本身。下面这段代码,我们想要记录函数foo被调用的次数:
function foo(num){
console.log("foo: " + num);
this.count++; //记录foo被调用的次数
}
foo.count = 0;
for(var i=0; i<10; i++){
if(i > 5){
foo(i)
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log(foo.count); // 0 为什么会是0呢?
foo()函数中的console.log语句产生了4条输出,证明foo()确实被调用了4次,但foo.count仍然是0,所以,仅从字面上来理解,this指向函数自身是错误的。那么,问题的原因是什么呢?
foo()函数是在全局作用域下执行的,this在这段代码中其实指向window,并且这段代码在无意中创建了一个全局变量count,他的值为NaN。
那么,遇到这样的问题许多的开发者(包括我),不会深入的思考为什么this的行为和预期的不一致,也不会回答那些很难解决,但非常重要的问题。这里提供了三种解决这个问题的方法,其中前两种方法回避了this的含义和工作原理。代码如下:
方法一 运用作用域(词法作用域)方法,该方法解决了我们遇到的问题,但是却没有直面this。
function foo(num){
console.log("foo: " + num);
data.count++; //记录foo被调用的次数
}
var data ={
count: 0
};
for(var i=0; i<10; i++){
if(i > 5){
foo(i);
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log(data.count);// 4
方法二 创建一个指向函数对象的词法标识符(变量)来引用它。同样该方法仍旧回避了this的问题。
function foo(num){
console.log("foo: " + num);
foo.count++; // foo指向它自身
}
foo.count = 0;
for(var i=0; i<10; i++){
if(i > 5){
foo(i);
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log(foo.count);// 4
方法三 既然我们知道this在foo函数执行时指向了别处,那么我们需要做的就是强制this指向foo函数.
function foo(num){
console.log("foo: " + num);
this.count++;
}
foo.count = 0;
for(var i=0; i<10; i++){
if(i > 5){
foo.call(foo, i); //使用call()可以确保this指向函数本身
}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
console.log(foo.count);// 4
这次我们从this的角度解决了问题。
误解2.指向函数作用域
第二种常见的误解是:this指向函数作用域。这个问题有点复杂,因为在某种情况下它是正确的,但在其他情况下他却是错误的。
但一定要明白,this在任何情况下都不指向函数的作用域,在javascript内部作用域和对象确实很相似,可见的标识符都是他的属性,但作用域“对象”无法通过JavaScript代码访问,它存在于JavaScript引擎内部
在文中给出了这样一段代码:
function foo(){
var a = 2;
this.bar();
}
function bar(){
console.log(this.a);
}
foo(); // ReferenceError: a is not defined
这段代码试图通过this联通foo()和bar()的词法作用域,从而让bar()可以访问foo()作用域的变量a。but it's impossible!
三、this到底是个什么玩意?
通过排除以上种种的误解,我们可以得出以下结论:
this是在运行时进行绑定的,并不是在编写时绑定的。
this的绑定和函数声明的位置没有关系,只取决于函数的调用方式。
具体细节是:当一个函数被调用时,会创建一个活动记录(也称执行上下文(context))。这个记录会包含一些信息,比如: 函数在哪里被调用(调用栈), 函数的调用方式, 传入的参数等,而this就是这个记录的一个属性,会在函数执行过程中被用到。
四、总结
随着你使用的模式越来越复杂,显式传递上下文对象会让代码变得越来越混乱。因此,通过使用this隐式传递可以将API设计的更加简洁并且易于复用。
this既不指向函数自身,也不指向函数的作用域。
this实际上是函数被调用时发生的绑定,它的指向完全取决于函数在哪里被调用。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。