前言
this 指向,即函数执行上下文,是我们在 JS 编程中无法避免的问题。一方面,更改 this 指向能达到我们的一些目的,例如,在类数组对象上使用数组方法:
const reduce = Array.prototype.reduce;
function sumArgs() {
return reduce.call(arguments, (sum, value) => {
return (sum += value);
});
}
console.log(sumArgs(1, 2, 3)); // => 6
但是另一方面,this 对象很难把握,经常会造成 this 指向不正确的问题。所以,今天整理一下,将 this 绑定到所需的值的一些方法。
首先,我们先定义一个辅助函数execute(fn)
,它仅作为参数提供的函数。
function execute(fn) {
return fn();
}
execute(function() {
console.log("hello,world!");
}); // => hello,world!
方法分离
当利用构造函数创建一个实例时,在 new 的时候。构造函数内部的 this 表示的是新创建的实例。
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
// 返回全名
this.getFullName = function() {
this === beauty; // => true
return `${this.firstName} ${this.lastName}`;
};
}
const beauty = new Person("Cassiel", "Lee");
console.log(beauty.getFullName()); // => "Cassiel" "Lee"
上述代码看起来好像符合了我们的预期,但实际上真是这样吗?我们尝试一下用之前定义的 execute 函数来执行一下试试看:
console.log(execute(beauty.getFullName)); // => "undefined" "undefined"
我们可以看到用 execute 辅助函数的方法却不能实现我们的目标,这是为什么呢?
这就是因为 this 指向不正确的原因。如果是采用beauty.getFullName
的方式,this 的指向是beauty
对象,所以可以获取到全民,但是包裹了一层execute
之后,getFullName
的方法中this
指向的是全局对象(浏览器环境中的 window )。this
等于 window
,${window.firstName} ${window.lastName}
执行结果是 undefined undefined
。发生这种情况是因为在调用 execute(beauty.getFullName)
时该方法与对象分离。当方法被分离,然后执行的时候,this 与原始对象没有连接。所以上述代码等价于:
const getFullNameSeparated = beauty.getFullName;
execute(getFullNameSeparated); // => 'undefined undefined'
方法分离问题,以及由此导致 this 指向不正确,一般出现几种情况如下:
- 回调
// `methodHandler()`中的`this`是全局对象
setTimeout(object.handlerMethod, duration);
- 在设置事件处理程序时
// React: `methodHandler()`中的`this`是全局对象
<button onClick={object.handlerMethod}>Click me</button>
所以,为了保证函数内部的 this 指向正确的对象,我们可以:
- 以属性访问器的形式执行方法,例如:
beauty.getFullName()
- 或者静态地将 this 绑定到包含的对象(使用箭头函数、bind()、call()等方法)
保证 this 指向正确对象的方法总结
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
const self = this;
// 返回全名
this.getFullName = function() {
self === beauty; // => true
return `${self.firstName} ${self.lastName}`;
};
}
const beauty = new Person("Cassiel", "Lee");
console.log(beauty.getFullName()); // => "Cassiel" "Lee"
console.log(execute(beauty.getFullName)); // => => "Cassiel" "Lee"
使用箭头函数
有没有办法在没有附加变量的情况下静态绑定 this? 当然可以,这就可以利用箭头函数实现。箭头函数是 ES6 中出现的新内容,了解的朋友都知道,箭头函数本身没有 this 指向,他的他 this 来自其定义的外部函数 this 的值。所以箭头函数是以词法方式绑定 this。这种方法简单又方便,所以建议在需要使用外部函数上下文的所有情况下都使用箭头函数。上述例子可以用箭头函数改写:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
// 返回全名
this.getFullName = () => `${this.firstName} ${this.lastName}`;
}
const beauty = new Person("Cassiel", "Lee");
console.log(beauty.getFullName()); // => "Cassiel" "Lee"
console.log(execute(beauty.getFullName)); // => => "Cassiel" "Lee"
绑定上下文
在类的情况下,使用附加的变量 self 或箭头函数来修复 this 的指向是行不通的。这时候就需要用到 bind,bind()等方法的作用就是将 this 对象绑定到指定对象上,所以利用 bind()对 this 指向进行手动绑定可以达到我们的目的。
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = this.getFullName.bind(this);
}
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
}
const beauty = new Person("Cassiel", "Lee");
console.log(beauty.getFullName()); // => "Cassiel" "Lee"
console.log(execute(beauty.getFullName)); // => => "Cassiel" "Lee"
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = this.getFullName.bind(this);
}
getFullName = () => {
return `${this.firstName} ${this.lastName}`;
};
}
const beauty = new Person("Cassiel", "Lee");
console.log(beauty.getFullName()); // => "Cassiel" "Lee"
console.log(execute(beauty.getFullName)); // => => "Cassiel" "Lee"
参考
结语
到这里,更改 this 指向的方法就梳理完毕了,this 指向是 JS 知识中非常重要的一部分,很多人觉得他很复杂,但是搞清楚之后其实没有我们想象的那么复杂。感谢那些发布帖子的前辈们,让我可以学习到这么多知识,前端的学习之路道阻且长,我也会继续努力的!