Javascript this 的一些学习总结


Posted in Javascript onAugust 02, 2012

1.1.1 摘要
相信有C++、C#或Java等编程经验的各位,对于this关键字再熟悉不过了。由于Javascript是一种面向对象的编程语言,它和C++、C#或Java一样都包含this关键字,接下来我们将向大家介绍Javascript中的this关键字。
本文目录
全局代码中的this
函数中的this
引用类型
函数调用以及非引用类型
引用类型以及this的null值
函数作为构造器被调用时this的值
手动设置函数调用时this的值

1.1.2 正文

由于许多面向对象的编程语言都包含this关键字,我们会很自然地把this和面向对象的编程方式联系在一起,this通常指向利用构造器新创建出来的对象。而在ECMAScript中,this不仅仅只用来表示创建出来的对象,也是执行上下文的一个属性:

activeExecutionContext = { 
// Variable object. 
VO: {...}, 
this: thisValue 
};

全局代码中的this
// Global scope 
// The implicit property of 
// the global object 
foo1 = "abc"; 
alert(foo1); // abc 
// The explicit property of 
// the global object 
this.foo2 = "def"; 
alert(foo2); // def 
// The implicit property of 
// the global object 
var foo3 = "ijk"; 
alert(foo3); // ijk

前面我们通过显式和隐式定义了全局属性foo1、foo2和foo3,由于this在全局上下文中,所以它的值是全局对象本身(在浏览器中是window object);接下来我们将介绍函数中的this。
函数中的this
当this在函数代码中,情况就复杂多了,并且会引发很多的问题。
函数代码中this值的第一个特性(同时也是最主要的特性)就是:它并非静态的绑定在函数上。
正如此前提到的,this的值是在进入执行上下文(Excution context)的阶段确定的,并且在函数代码中的话,其值每次都不尽相同。
然而,一旦进入执行代码阶段,其值就不能改变了。如果要想给this赋一个新的值是不可能的,因为在那时this根本就不是变量了。
接下来,我们通过具体的例子说明函数中的this。
首先我们定义两个对象foo和person,foo包含一个属性name,而person包含属性name和方法say(),具体的定义如下:
// Defines foo object. 
var foo = { 
name: "Foo" 
}; 
// Defines person object. 
var person = { 
name: "JK_Rush", 
say: function() { 
alert(this === person); 
alert("My name is " + this.name); 
} 
}; 
person.say(); // My name is JK_Rush 
// foo and person object refer to 
// the same function say 
foo.say = person.say; 
foo.say(); // My name is Foo.

通过上面的代码,我们发现调用person的say()方法时,this指向person对象,当通过赋值方式使得foo的say()方法指向peson中的say()方法时。我们调用foo的say()方法,发现this不是指向person对象,而不是指向foo对象,这究竟是什么原因呢?
首先,我们必须知道this的值在函数中是非静态的,它的值确定在函数调用时,具体代码执行前,this的值是由激活上下文代码的调用者决定的,比如说,调用函数的外层上下文;更重要的是,this的值是由调用表达式的形式决定的,所以说this并非静态的绑定在函数上。
由于this并非静态地绑定在函数上,那么我们是否可以在函数中动态地修改this的值呢?
// Defines foo object. 
var foo = { 
name: "Foo" 
}; 
// Defines person object. 
var person = { 
name: "JK_Rush", 
say: function() { 
alert(this === person); 
this = foo; // ReferenceError 
alert("My name is " + this.name); 
} 
}; 
person.say(); // My name is JK_Rush

现在我们在方法say()中,动态地修改this的值,当我们重新执行以上代码,发现this的值引用错误。这是由于一旦进入执行代码阶段(函数调用时,具体代码执行前),this的值就确定了,所以不能改变了。
引用类型
前面我们提到this的值是由激活上下文代码的调用者决定的,更重要的是,this的值是由调用表达式的形式决定的;那么表达式的形式是如何影响this的值呢?
首先,让我们介绍一个内部类型——引用类型,它的值可以用伪代码表示为一个拥有两个属性的对象分别是:base属性(属性所属的对象)以及该base对象中的propertyName属性:
// Reference type. 
var valueOfReferenceType = { 
base: mybase, 
propertyName : 'mybasepropertyName' 
};

引用类型的值只有可能是以下两种情况:
当处理一个标识符的时候
或者进行属性访问的时候
标识符其实就是变量名、函数名、函数参数名以及全局对象的未受限的属性。
// Declares varible. 
var foo = 23; 
// Declares a function 
function say() { 
// Your code. 
}

中间过程中,对应的引用类型如下:
// Reference type. 
var fooReference = { 
base: global, 
propertyName: 'foo' 
}; 
var sayReference = { 
base: global, 
propertyName: 'say' 
};

我们知道Javascript中属性访问有两种方式:点符号和中括号符号:
// Invokes the say method. 
foo.say(); 
foo['say']();

由于say()方法是标识符,所以它对应于foo对象引用类型如下:
// Reference type. 
var fooSayReference = { 
base: foo, 
propertyName: 'say' 
};

我们发现say()方法的base属性值为foo对象,那么它对应的this属性也将指向foo对象。
假设,我们直接调用say()方法,它对应的引用类型如下:
// Reference type. 
var sayReference = { 
base: global, 
propertyName: 'say' 
};

由于say()方法的base属性值为global(通常来说是window object),那么它对应的this属性也将指向global。
函数上下文中this的值是函数调用者提供并且由当前调用表达式的形式而定的。如果在调用括号()的左边有引用类型的值,那么this的值就会设置为该引用类型值的base对象。 所有其他情况下(非引用类型),this的值总是null。然而,由于null对于this来说没有任何意义,因此会隐式转换为全局对象。
函数调用以及非引用类型
前面我们提到,当调用括号左侧为非引用类型的时,this的值会设置为null,并最终隐式转换为全局对象。
现在我们定义了一个匿名自执行函数,具体实现如下:
// Declares anonymous function 
(function () { 
alert(this); // null => global 
})();

由于括号()左边的匿名函数是非引用类型对象(它既不是标识符也不属于属性访问),因此,this的值设置为全局对象。
// Declares object. 
var foo = { 
bar: function () { 
alert(this); 
} 
}; 
(foo.bar)(); // foo. 
(foo.bar = foo.bar)(); // global? 
(false || foo.bar)(); // global? 
(foo.bar, foo.bar)(); // global

这里注意到四个表达式中,只有第一个表达式this是指向foo对象的,而其他三个表达式则执行global。
现在我们又有疑问了:为什么属性访问,但是最终this的值不是引用类型对象而是全局对象呢?
我们注意到表达式二是赋值(assignment operator),与表达式一组操作符不同的是,它会触发调用GetValue方法(参见11.13.1中的第三步)。 最后返回的时候就是一个函数对象了(而不是引用类型的值了),这就意味着this的值会设置为null,最终会变成全局对象。
第三和第四种情况也是类似的——逗号操作符和OR逻辑表达式都会触发调用GetValue方法,于是相应地就会丢失原先的引用类型值,变成了函数类型,this的值就变成了全局对象了。
引用类型以及this的null值
对于前面提及的情形,还有例外的情况,当调用表达式左侧是引用类型的值,但是this的值却是null,最终变为全局对象(global object)。 发生这种情况的条件是当引用类型值的base对象恰好为活跃对象(activation object)。
当内部子函数在父函数中被调用的时候就会发生这种情况,通过下面的示意代码介绍活跃对象:
// Declares foo function. 
function foo() { 
function bar() { 
alert(this); // global 
} 
// The same as AO.bar(). 
bar(); 
}

由于活跃对象(activation object)总是会返回this值为——null(用伪代码来表示AO.bar()就相当于null.bar()),然后,this的值最终会由null转变为全局对象。
当函数调用包含在with语句的代码块中,并且with对象包含一个函数属性的时候,就会出现例外的情况。with语句会将该对象添加到作用域链的最前面,在活跃对象的之前。 相应地,在引用类型的值(标识符或者属性访问)的情况下,base对象就不再是活跃对象了,而是with语句的对象。另外,值得一提的是,它不仅仅只针对内部函数,全局函数也是如此, 原因就是with对象掩盖了作用域链中更高层的对象(全局对象或者活跃对象):
函数作为构造器被调用时this的值
函数作为构造函数时,我们通过new操作符创建实例对象是,它会调用Foo()函数的内部[[Construct]]方法;在对象创建之后,会调用内部的[[Call]]方法,然后所有Foo()函数中this的值会设置为新创建的对象。
// Declares constructor 
function Foo() { 
// The new object. 
alert(this); 
this.x = 10; 
} 
var foo = new Foo(); 
foo.x = 23; 
alert(foo.x); // 23手动设置函数调用时this的值

Function.prototype原型上定义了两个方法,允许手动指定函数调用时this的值。这两个方法分别是:.apply()和.call()。这两个方法都接受第一个参数作为调用上下文中this的值,而这两个方法的区别是传递的参数,对于.apply()方法来说,第二个参数接受数组类型(或者是类数组的对象,比如arguments), 而.call()方法接受任意多的参数(通过逗号分隔);这两个方法只有第一个参数是必要的——this的值。
通过示例代码介绍call()方法和apply()方法的使用:
var myObject = {}; 
var myFunction = function(param1, param2) { 
//setviacall()'this'points to my Object when function is invoked 
this.foo = param1; 
this.bar = param2; 
//logs Object{foo = 'foo', bar = 'bar'} 
console.log(this); 
}; 
// invokes function, set this value to myObject 
myFunction.call(myObject, 'foo', 'bar'); 
// logs Object {foo = 'foo', bar = 'bar'} 
console.log(myObject);

call()方法第一个参数是必要的this值,接着我们可以传递任意多个参数,接着介绍apply()方法的使用。
var myObject = {}; 
var myFunction = function(param1, param2) { 
//set via apply(), this points to my Object when function is invoked 
this.foo=param1; 
this.bar=param2; 
// logs Object{foo='foo', bar='bar'} 
console.log(this); 
}; 
// invoke function, set this value 
myFunction.apply(myObject, ['foo', 'bar']); 
// logs Object {foo = 'foo', bar = 'bar'} 
console.log(myObject);

通过与call()方法对比,我们发现apply()方法和call()方法没有太大的区别,只是方法签名不一样。

1.1.3 总结

本文介绍Javascript中this的使用,更重要的是帮助我们能更好地理解this值在全局、函数、构造函数以及一些特例的情况中值的变化。

对于在函数上下文中this的值是函数调用者提供并且由当前调用表达式的形式而定的。如果在调用括号()的左边有引用类型的值,那么this的值就会设置为该引用类型值的base对象。 所有其他情况下(非引用类型),this的值总是null。然而,由于null对于this来说没有任何意义,因此会隐式转换为全局对象。
对于特例情况,我们要记住赋值符、逗号操作符以及||逻辑表达式,会使this丢失原先的引用类型值,变成了函数类型,this的值就变成了全局对象了

参考
[1] http://dmitrysoshnikov.com/ecmascript/chapter-3-this/ 英文版
[2] http://blog.goddyzhao.me/post/11218727474/this 译文
[3] https://net.tutsplus.com/tutorials/javascript-ajax/fully-understanding-the-this-keyword/

[作者]: JK_Rush

Javascript 相关文章推荐
15款优秀的jQuery导航菜单插件分享
Jul 19 Javascript
解决js正则匹配换行问题实现代码
Dec 10 Javascript
Extjs4 GridPanel的主要配置参数详细介绍
Apr 18 Javascript
Jquery easyUI 更新行示例
Mar 06 Javascript
简介JavaScript中的sub()方法的使用
Jun 08 Javascript
基于Jquery+div+css实现弹出登录窗口(代码超简单)
Oct 27 Javascript
基于JavaScript代码实现随机漂浮图片广告
Jan 05 Javascript
浅析jQuery操作select控件的取值和设值
Dec 07 Javascript
JavaScrpt的面向对象全面解析
May 09 Javascript
js字符串倒序的实例代码
Nov 30 Javascript
Node.js console控制台简单用法分析
Jan 04 Javascript
JavaScript canvas实现跟随鼠标事件
Feb 10 Javascript
创建公共调用 jQuery Ajax 带返回值
Aug 01 #Javascript
这些年、我收集的JQuery代码小结
Aug 01 #Javascript
JQquery的一些使用心得分享
Aug 01 #Javascript
javascript 兼容所有浏览器的DOM扩展功能
Aug 01 #Javascript
别了 JavaScript中的isXX系列
Aug 01 #Javascript
JS判断元素为数字的奇异写法分享
Aug 01 #Javascript
javascript for循环从入门到偏门(效率优化+奇特用法)
Aug 01 #Javascript
You might like
PHP中使用unset销毁变量并内存释放问题
2012/07/05 PHP
Php中用PDO查询Mysql来避免SQL注入风险的方法
2013/04/25 PHP
使用PHP生成PDF方法详解
2015/01/23 PHP
PHP进制转换实例分析(2,8,16,36,64进制至10进制相互转换)
2017/02/04 PHP
实例讲解PHP页面静态化
2018/02/05 PHP
鼠标滑过出现预览的大图提示效果
2014/02/26 Javascript
详解JavaScript中undefined与null的区别
2014/03/29 Javascript
使用jQuery中的when实现多个AJAX请求对应单个回调的例子分享
2014/04/23 Javascript
JavaScript实现点击自动选择TextArea文本的方法
2015/07/02 Javascript
vuejs指令详解
2017/02/07 Javascript
Vue.js学习记录之在元素与template中使用v-if指令实例
2017/06/27 Javascript
Vue实现动态添加或者删除对象和对象数组的操作方法
2018/09/21 Javascript
小程序两种滚动公告栏的实现方法
2019/09/17 Javascript
跟老齐学Python之集合(set)
2014/09/24 Python
python实现多线程抓取知乎用户
2016/12/12 Python
numpy中以文本的方式存储以及读取数据方法
2018/06/04 Python
Flask之flask-script模块使用
2018/07/26 Python
Python列表list排列组合操作示例
2018/12/18 Python
Pandas之MultiIndex对象的示例详解
2019/06/25 Python
Flask使用Pyecharts在单个页面展示多个图表的方法
2019/08/05 Python
python tkinter控件布局项目实例
2019/11/04 Python
Python基于爬虫实现全网搜索并下载音乐
2021/02/14 Python
Html5嵌入钉钉的实现示例
2020/06/04 HTML / CSS
新西兰领先的内衣店:Bendon Lingerie新西兰
2018/07/11 全球购物
Hotels.com拉丁美洲:从豪华酒店到经济型酒店的预定优惠和折扣
2019/12/09 全球购物
电子商务自荐书范文
2014/01/04 职场文书
护理专业毕业生自荐信范文
2014/01/05 职场文书
在校生自我鉴定
2014/01/23 职场文书
军训自我鉴定怎么写
2014/02/13 职场文书
社会稳定风险评估方案
2014/06/02 职场文书
爱心捐书活动总结
2014/07/05 职场文书
搞笑欢迎词大全
2015/09/30 职场文书
2019年教师节活动策划方案
2019/09/09 职场文书
redis不能访问本机真实ip地址的解决方案
2021/07/07 Redis
nginx中封禁ip和允许内网ip访问的实现示例
2022/03/17 Servers
openEuler 搭建java开发环境的详细过程
2022/06/10 Servers