JavaScript 是一种脚本语言,因此被很多人认为是简单易学的。然而情况恰恰相反,JavaScript 支持函数式编程、闭包、基于原型的继承等高级功能。JavaScript 中的 this 关键字在不同情况下所指的含义均不相同。
在 Java 等面向对象的语言中,this 关键字的含义是明确且具体的,即指代当前对象。一般在编译期确定下来,或称为编译期绑定。而在 JavaScript 中,this 是动态绑定,或称为运行期绑定的,这就导致 JavaScript 中的 this 关键字有能力具备多重含义,带来灵活性的同时,也为带来不少困惑。
1. Java 语言中的 this
在 Java 中定义类经常会使用 this 关键字,多数情况下是为了避免命名冲突,比如在下面例子的中,定义一个 Point 类,很自然的,大家会使用 x,y 为其属性或成员变量命名,在构造函数中,使用 x,y 为参数命名,相比其他的名字,比如 a,b,也更有意义。这时候就需要使用 this 来避免命名上的冲突。另一种情况是为了方便的调用其他构造函数,比如定义在 x 轴上的点,其 x 值默认为 0,使用时只要提供 y 值就可以了,我们可以为此定义一个只需传入一个参数的构造函数。无论哪种情况,this 的含义是一样的,均指当前对象。
代码1:
public class Point {
private int x = 0;
private int y = 0;
public Point(x, y){
this.x = x;
this.y = y;
}
public Point(y){
this(0, y);
}
}
2. JavaScript 语言中的 this
由于其运行期绑定的特性,JavaScript 中的 this 含义要丰富得多,它可以是全局对象、当前对象或者任意对象,这完全取决于函数的调用方式。JavaScript 中函数的调用有以下几种方式:作为对象方法调用,作为函数调用,作为构造函数调用,和使用 apply 或 call 调用。下面将按照调用方式的不同,分别讨论 this 的含义。
(1)作为函数的this
函数也可以直接被调用,此时 this 绑定到全局对象。在浏览器中,window 就是该全局对象。比如下面的例子:函数被调用时,this 被绑定到全局对象,接下来执行赋值语句,相当于隐式的声明了一个全局变量,这显然不是调用者希望的。
代码2:
function click_this(x){
this.x = x;
var temp = "";
}
Firefox调试结果如下:
(2)作为对象的this
在JavaScript中,函数也是对象,因此函数也可以作为一个对象的属性,此时该函数被称为该对象的方法,在使用这种方式调用时,this被自然绑定到该对象上。如下代码所示:
代码3:
var point = {
x : 0,
y : 0,
move : function(x, y){
this.x += x;
this.y += y;
}
};
point.move(10, 20);
Firefox中调试结果如下图:
(3)作为对象函数的内部函数的this
当javascript对象中有函数时,函数内部又定义了一个内部函数,这时内部函数的this将会绑定全局对象即window而非对象本身。代码示例如下:
代码4:
var point = {
x : 0,
y : 0,
move : function(x, y){
function moveTo(x, y){
this.x += x;
this.y += y;
}
moveTo(x, y);
}
};
point.move(10, 20);
Firefox调试结果如下:
这属于 JavaScript 的设计缺陷,正确的设计方式是内部函数的 this 应该绑定到其外层函数对应的对象上,为了规避这一设计缺陷,聪明的 JavaScript 程序员想出了变量替代的方法,约定俗成,该变量一般被命名为 that。
(4)使用 apply 或 call 调用
在 JavaScript 中函数也是对象,对象则有方法,apply 和 call 就是函数对象的方法。这两个方法异常强大,他们允许切换函数执行的上下文环境(context),即 this 绑定的对象。很多 JavaScript 中的技巧以及类库都用到了该方法。
在使用apply和call中使用this时,当作为函数调用时,this绑定的是全局对象即window;当作为方法调用时,this绑定的是对象本身。
3. 函数的执行环境
JavaScript 中的函数既可以被当作普通函数执行,也可以作为对象的方法执行,这是导致 this 含义如此丰富的主要原因。一个函数被执行时,会创建一个执行环(Execution Context),函数的所有的行为均发生在此执行环境中,构建该执行环境时,JavaScript 首先会创建 arguments变量,其中包含调用函数时传入的参数。接下来创建作用域链。然后初始化变量,首先初始化函数的形参表,值为 arguments变量中对应的值,如果 arguments变量中没有对应值,则该形参初始化为 undefined。如果该函数中含有内部函数,则初始化这些内部函数。如果没有,继续初始化该函数内定义的局部变量,需要注意的是此时这些变量初始化为 undefined,其赋值操作在执行环境(Execution Context)创建成功后,函数执行时才会执行,这点对于理解 JavaScript 中的变量作用域非常重要。最后为 this变量赋值,如前所述,会根据函数调用方式的不同,赋给 this全局对象,当前对象等。至此函数的执行环境(Execution Context)创建成功,函数开始逐行执行,所需变量均从之前构建好的执行环境(Execution Context)中读取。
参考资料:
深入浅出 JavaScript 中的 this---
http://www.ibm.com/developerworks/cn/web/1207_wangqf_jsthis/#toggle