JavaScript中this关键字含义及其最佳实践(一)
在JavaScript中this
关键字非常灵活, 在不同的情况下有不同的含义. 对于初学者来说 比较难以掌握, 即使是有经验的开发人员, 亦容易犯错. Google 一下 understanding this keyword in javascript
有一大把相关的网页, 足以证明this
给开发者带来的困 扰.
this
含义总则
万剑归宗, 万宗归一. 总得来说,
this
指定义处(非运行处)上下文对象.
不同情景
下面根据不同的情景阐述一下.
- 纯函数
- 对象方法
- 类构造函数/类实例方法
- call/apply/bind
- 事件监听器
纯函数
在浏览器中运行以下示例, 会发现, this
都是指向window
对象.
var test = function (value) { this.hello = value; console.log(‘this in test() ‘) console.log(this); console.log(window.hello); }; var obj = {}; var wrapper = function (value) { console.log(‘this in wrapper() ‘); console.log(this); test(value || ‘value from wrapper‘); }; //在全局下运行, 故`this`指向全局对象, 浏览器中即windows. //若运行在node.js环境中, 指向global test(‘value from window‘); //同test() wrapper(); //此时, 使用call改变了wrapper函数中`this`的指向. //在wrapper函数中, `this`指向 obj 对象. // //虽然在wrapper函数中运行test函数, 但在test函数中`this`依然指向全局对象. //这种情况很好的说明`this`的含义总则: 指定义处(非运行处)上下文对象. // wrapper.call(obj, ‘value from wrapper apply‘);
对象方法
在对象方法中, this
总是指向对象本身.
var uncleWang = { _name: ‘WangXiaobo‘, getName: function () { //`this` 指向uncleWang 对象 return this._name; }, displayName: function () { //`this` 指向uncleWang 对象 var name = this.getName(); //显示 The name is WangXiaobo console.log(‘The name is ‘ + name); } };
若一个纯函数变成了一个对象的方法, 函数作为对象方法时, this
指向对象. 但作为纯函 数调用时, this
依然指向定义处的上下文对象.
window._name = ‘window‘; var sayName = function () { console.log(‘My name is ‘ + this._name); } uncleWang.sayName = sayName; //作为对象方法调用, `this`指向对象 //显示 My name is WangXiaobo uncleWang.sayName(); //纯函数调用, `this`指向定义处的上下文对象 //显示 My name is window sayName();
类构造函数/类实例方法
- 在类构造函数和类实例方法中,
this
都指向类实例. - 在类方法中,
this
指向构造函数本身, 因为构造函数本身也是对象.- 在JS中, 除了基本类型外, 一切皆对象
- 函数也是对象, 构造函数也是对象.
/** * @param {String} name */ function Person (name) { this._name = name; } //类方法添加至构造函数的prototype对象上 Person.prototype.getName = function () { return this._name; }; Person.prototype.displayName = function () { var name = this.getName(); console.log(‘name: ‘ + name); }; Person._className = ‘Person‘; //类方法中, `this`指向构造函数本身, 因为构造函数本身也是对象. Person.getNameOfClass = function () { return this._className; }; console.log(‘name of class: ‘ + Person.getNameOfClass()); //使用new创建类实例 var uncleWang = new Person(‘WangXiaobo‘); uncleWang.displayName();
call/apply/bind
在JavaScript中, 函数有call, apply和bind方法, 都可以用于改变函数的执行上下文. 有 关这些方法详细说明可参考:
- Function.prototype.apply() - JavaScript | MDN
- Function.prototype.call() - JavaScript | MDN
- Function.prototype.bind() - JavaScript | MDN
在函数中, this
指向这些方法绑定的对象, 如果绑定的对象是undefined
或者null
, this
指向全局上下文.
window.hello = ‘hello of window‘; var obj = { hello: ‘hello of obj‘ }; var sayHello = function () { console.log(this.hello); }; //纯函数执行, `this`指向定义处上下文 //显示 hello of window sayHello(); //call, apply 方法运行函数并使得运行时函数中`this`指向obj对象 sayHello.call(obj); sayHello.apply(obj); //bind返回一个包装过的函数 var bindedSayHello = sayHello.bind(obj); //执行包装函数时, 会调用sayHello函数, 且sayHello函数中`this`指向obj对象 bindedSayHello(); // var obj2 = { hello: ‘hello of obj2‘ }; //即使使用call或apply改变包装函数中`this`的上下文, 但执行sayHello函数时, sayHello函 //数中`this`依然指向obj对象 //有一点点难于理解, 是吧? bindedSayHello.call(obj2);
bind有时难于理解, 可以参考以下bind的简单实现以理解其原理.
/** * @param {Function} fn * @param {Object} context * @return {Function} */ var bind = function (fn, context) { return function () { fn.apply(context, arguments); }; } var bindedSayHello2 = bind(sayHello, obj); bindedSayHello2();
事件监听器
由于各个浏览器实现的不一致, DOM事件监听器中的this
会给开发者带来一点困扰.
绑定事件监听器的方式
Html元素绑定事件监听器的方式一般有以下几种:
- 行内绑定
- 在html 元素的属性上直接设置, 比如
<a ... onclick="alert(‘link‘)" ...
- 在html 元素的属性上直接设置, 比如
- DOM Level 0 方式绑定
- 使用JS设置元素属性方式, 如
link.onclick = function () {...};
- 使用JS设置元素属性方式, 如
- DOM Level 2 方式绑定
- 使用
addEventListener
方法绑定事件 - 只适合支持w3c DOM 2规范的浏览器
- 使用
- IE
- ie9 之前版本(不包含ie9) 使用
attachEvent
方法绑定事件 - ie9+ 可以使用DOM Level 2 方式绑定事件
- ie9 之前版本(不包含ie9) 使用
测试
可以用以下html代码测试在不同浏览器下, 使用不同方式绑定的监听器中this
的指向.
<!DOCTYPE html> <html> <head> <title>test this</title> <meta charset="utf-8" /> </head> <body> <h1> test this </h1> <div> <p id="inline" onclick="console.log(this)" > <input type="button" value="test inline" /> </p> <p id="dom0" > <input type="button" value="test dom level 0" /> </p> <p id="dom2-w3c" > <input type="button" value="test dom level 2 for w3c" /> (only available in browser that supports DOM2) </p> <p id="dom-ie" > <input type="button" value="test dom level 2 for ie" /> (only available in IE) </p> </div> <script> var dom0= document.getElementById(‘dom0‘), dom2W3c = document.getElementById(‘dom2-w3c‘), domIe = document.getElementById(‘dom-ie‘); dom0.onclick = function () { console.log(this); }; if (domIe.attachEvent) { domIe.attachEvent(‘onclick‘, function () { console.log(this); }, false); } if (dom2W3c.addEventListener != null) { dom2W3c.addEventListener(‘click‘, function () { console.log(this); }, false); } </script> </body> </html>
如果在不同的浏览器测试以上代码, 可以总结做成以下图表.
浏览器 | 行内绑定 | DOM Level 0 | addEventListener | attachEvent |
---|---|---|---|---|
w3c 系列 | Element | Element | Element | X |
ie 系列 | Element | Element | Element (ie9+) | Window |
Element
:this
指向被绑定的元素Window
:this
指向window
对象X
: 浏览器不支持某些特性, 无法测试
结论
根据以上图表, 我们很容易得出结论:
- 在IE中使用
attachEvent
方式绑定的监听器中,this
指向window
对象. - 在其他方式绑定的监听器中,
this
指向被绑定的元素.- 注意: 被绑定的元素, 并不是
event.target
, 虽然在某些情况下二者可能是同一对象.
- 注意: 被绑定的元素, 并不是
扩展阅读
- Javascript的this用法 - 阮一峰的网络日志
- Function.prototype.apply() - JavaScript | MDN
- Function.prototype.call() - JavaScript | MDN
- Function.prototype.bind() - JavaScript | MDN
- Javascript - The this keyword
- Javascript - Early event handlers
- Javascript - Traditional event registration model
- Javascript - Advanced event registration models
@end
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。