这几天在闭门深入研究原生JavaScript与DOM,反复阅读了几本书之后,遇到了几个难点与困惑之处,比如this、闭包、原型继承等语言特性。今天来说下this。
this是js中最令人迷惑的地方之一了,初学者很容易把this的值和作用域联系到一起,其实this的值只与调用时函数所处的执行环境(execution context)相关,与函数作用域没有一点关系。常常有以下几种情景或者函数调用模式可以判别this所指的值:
当函数作为对象的方法时,this所指的即为包含该方法的那个对象。(函数方法顶层作用域中的this,不是嵌套函数nested function的情况)。这种情况比较常见,比如下面的代码:定义了一个rectangle对象,这个对象有两个属性和一个面积方法,调用时在方法体内的this被绑定到该对象rectangle。
var rectangle = { width : 20, height : 10, area : function(){ return this.width * this.height; //当调用area()方法时,this的值为该对象,也就是rectangle } //返回值语句可以替换为 return rectangle.width * rectangle.height; }; rectangle.area() //输出200
如果我想要给rectangle对象再添加一个计算周长的方法,并且在方法内部又定义了一个用于计算的函数(嵌套函数),那么这个函数中的this将是怎么样的呢?倘若我在此calculator函数中直接使用this,那么这个嵌套函数(nested function)的this值将被绑定到全局对象window!
Douglas Crockford对这种完全相同的情况有一番解释,“当函数以此模式调用时,this被绑定到全局对象!这是语言设计上的一个错误,当内部函数被调用时,this应该仍然绑定到外部函数的this变量。这个设计错误的后果是方法不能利用内部函数来帮助它工作,因为内部函数被绑定了错误的值,所以不能共享该方法对对象的访问权。”嗯,牛人可以直指语言的设计错误,我们只要寻求解决方案就可以啦:给该方法定义一个变量并给它赋值为this,那么内部函数就可以通过那个变量访问到外围函数的this,也就是rectangle对象。这样,that就会携带者rectangle对象的引用而传递到了嵌套的函数中去了。
rectangle.circumference = function(){ //给对象增加一个计算周长的方法 var that = this; var calculator = function(){ //方法中的嵌套函数this将会绑定为window,用变量that传递this值为解决方案 return 2*that.width + 2*that.height; }; var value = calculator(); return value; } rectangle.circumference(); //输出60
当this出现在构造函数中,this的值即为刚new好的那个对象。如下面代码所示,Rectangle为长方形的函数构造器,初始化对象的输入为宽和高,那么通过this关键字,这两个值将会分别被赋值到新创建的对象的属性中去。对于构造函数this值绑定的实质是:“如果在一个函数前面带上new来调用,那么将创建一个隐藏链接到该函数的prototype成员的新对象,同时this将会被绑定到那个新对象上去。”
function Rectangle(width, height){ //Rectancle函数构造器 this.width = width; this.height = height; }; Rectangle.prototype.area = function(){ return this.width * this.height; }; var someR1 = new Rectangle(20,10); var someR2 = new Rectangle(40,20); someR1.area(); //200 someR2.area(); //800
在函数只是作为函数调用时,this值为全局变量window。其实对于上面条目一里的第二种情况就是内部函数作为外部方法的调用,this指向全局变量。下面这个例子定义了一个全局变量和全局函数,当函数直接调用时,this所指就是window对象;当函数作为对象的方法调用时,this指向o对象。此外,它也很好的解释了this的值只与执行上下文相关,而与函数本身或者说作用域是没有关系的。
//函数当做函数调用时,this指向全局变量window var global = '我是全局变量'; function f(global){ alert(this.global); } f('test'); //输出'我是全局变量' var o = { f : f, global : '对象中的变量' } o.f('test'); //输出'对象中的变量'
事件监听中,this指向该元素本身。嗯,这个应该很容易理解。
var element = document.getElementById('someElement'); element.onclick = function(){ alert(this); //输出相应的元素自身 如input元素[object HTMLInputElement] }
可以使用call()和apply()方法改变函数的执行环境为指定对象的环境,则this所指的就是那个指定的对象。以上面方法的嵌套函数中this的绑定为例,前面已经提到过,如果直接调用嵌套函数,那么其中的this将会指向全局变量window。现在我使用call()将函数的执行上下文绑定到rectangle对象上,那么,当该函数被调用时候,this值将总会指向rectangle。顺便说明下,call()和apply()没有本质区别,唯一的区别就是如果有形参的话前者接收的参数为逗号分割的变量,后者接收的参数为一数组。
rectangle.circumference = function(){ var calculator = function(){ return 2*this.width + 2*this.height; //注意这里是this而不是that }; var value = calculator.call(rectangle); //将执行环境绑定到rectangle对象,calculator中this的值即为rectangle return value; }; alert(rectangle.circumference()); //输出60
可以简单的根据函数的执行环境总结this的值为:
普通函数:全局对象window
对象的方法:该对象
构造函数:新创建的对象
call()、apply():指定的对象
几个有关this讨论的链接:
1.《JavaScript高级程序设计》译者李松峰在翻译中指出的原作者关于this见解的错误:http://www.cn-cuckoo.com/2010/05/08/about-one-sentence-of-pro-js-2nd-1626.html
2.Dmitry A. Soshnikov对于this比较透彻的讲解http://dmitrysoshnikov.com/ecmascript/chapter-3-this/
本博文转载自------http://hanzongze.info/blog/blog/2011/05/16/%E5%85%B3%E4%BA%8Ejavascript%E4%B8%AD%E7%9A%84this/