如何在回调函数中访问正确的 this
技术背景
在 JavaScript 里,this
关键字的表现与其他语言有所不同,并且在严格模式和非严格模式下也存在差异。其值主要由函数的调用方式决定,无法在执行期间通过赋值来设定,每次调用函数时 this
的值都可能不同。在回调函数中,this
的指向常常不符合预期,所以我们需要掌握一些方法来确保能访问到正确的 this
。
实现步骤
了解 this
的基本规则
this
(即“上下文”)是每个函数内部的特殊关键字,其值仅取决于函数的调用方式,而非定义方式、时间或位置。以下是一些示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| function foo() { console.log(this); }
foo();
var obj = {bar: foo}; obj.bar();
new foo();
|
访问正确的 this
的方法
使用箭头函数
ES6 引入了箭头函数,它可被视为 lambda 函数,没有自己的 this
绑定。this
会像普通变量一样在作用域中查找,因此无需调用 .bind
。
1 2 3 4
| function MyConstructor(data, transport) { this.data = data; transport.on('data', () => alert(this.data)); }
|
不使用 this
可以创建一个新变量来引用 this
所指向的对象,常见的变量名有 self
和 that
。
1 2 3 4 5 6 7
| function MyConstructor(data, transport) { this.data = data; var self = this; transport.on('data', function() { alert(self.data); }); }
|
显式设置回调函数的 this
(方法一)
每个函数都有 .bind
方法,它会返回一个新函数,该函数的 this
被绑定到指定的值。
1 2 3 4 5 6 7
| function MyConstructor(data, transport) { this.data = data; var boundFunction = (function() { alert(this.data); }).bind(this); transport.on('data', boundFunction); }
|
在 jQuery 中,可使用 jQuery.proxy
代替 .bind
。
显式设置回调函数的 this
(方法二)
有些接受回调函数的函数或方法也允许传入一个值,使回调函数的 this
指向该值,例如 Array#map
。
1 2 3 4 5 6
| var arr = [1, 2, 3]; var obj = {multiplier: 42};
var new_arr = arr.map(function(v) { return v * this.multiplier; }, obj);
|
处理对象方法作为回调/事件处理程序的问题
当对象方法作为回调/事件处理程序时,this
的指向可能不符合预期。解决方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function Foo() { this.data = 42; document.body.onclick = this.method.bind(this);
var self = this; document.body.onclick = function() { self.method(); };
document.body.onclick = () => this.method(); }
Foo.prototype.method = function() { console.log(this.data); };
|
核心代码
以下是一个综合示例,展示了多种访问正确 this
的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function Person(name) { this.name = name;
this.sayNameVersion1 = function(callback) { callback.bind(this)(); }; this.sayNameVersion2 = function(callback) { callback(); }; this.sayNameVersion3 = function(callback) { callback.call(this); }; this.sayNameVersion4 = function(callback) { callback.apply(this); }; }
function niceCallback() { var parentObject = this; console.log(parentObject); }
var p1 = new Person('zami'); p1.sayNameVersion1(niceCallback); p1.sayNameVersion2(niceCallback.bind(p1)); p1.sayNameVersion3(niceCallback); p1.sayNameVersion4(niceCallback);
|
最佳实践
- 优先使用箭头函数:箭头函数没有自己的
this
绑定,能保留外部作用域的 this
值,语法简洁,适用于大多数回调函数场景。 - 避免过度使用
this
:如果不是必需,尽量不使用 this
和类。对于单例模式的模块,可直接在文件根目录定义变量和函数,然后导出使用。 - 正确使用
.bind
、.call
和 .apply
:在需要显式设置 this
值时使用这些方法,但要注意避免过度使用,以免使代码变得复杂。
常见问题
setTimeout
中 this
的问题
setTimeout
总是以全局对象(Window
)执行回调函数,可使用 .bind
解决:
1 2 3
| setTimeout(function() { this.methodName(); }.bind(this), 2000);
|
箭头函数的限制
箭头函数不适合作为对象方法,也不能用作构造函数,因为它没有自己的 this
、arguments
、super
或 new.target
绑定。
类中 this
的问题
在类中使用回调函数时,可使用类字段和箭头函数来确保 this
的正确指向:
1 2 3 4 5 6 7 8 9
| class someView { onSomeInputKeyUp = (event) => { console.log(this); };
someInitMethod() { someInput.addEventListener('input', this.onSomeInputKeyUp); } }
|
不过,这是一个 Stage 3 提案,目前需要使用 Babel 和相应的插件进行处理。