箭头函数从定义它的最近的外层函数作用域中继承 this
箭头函数没有自己的 this 上下文,箭头函数从定义它的最近的外层函数作用域中继承 this。
借助这一句话可以理解所有涉及到箭头函数的this值,箭头函数的this值永远不会改变。
定时器
定时器中的this值
相信大家都听说过一句比较笼统的概括:定时器中的this值指向window。反正写这篇文章之前我一直都以为是这样的。为什么说它笼统呢?当然,它并不全面,甚至可能造成误解。
先上结论:
- 定时器的普通函数的
this值指向window\undefined。 - 定时器的箭头函数的
this保留为定义它时所在的词汇作用域。
看一段代码:
const obj = {
name: 'obj',
fn() {
setTimeout(function() {
console.log(this);// window
}, 0)
console.log(this === obj);// true
let that = this;
setTimeout(() => {
console.log(this === that);// true
console.log(this === obj);// true
console.log(this.name);// obj
}, 0);
}
}
obj.fn();
分析第一个定时器:
第一个定时器调用普通函数。至于普通函数不是我们今天的重点,下边这段分析也讲的够清楚:
所有超时执行的函数都会在全局作用域中的一个匿名函数中运行,因此函数中的
this值在非严格模式下始终指向window。
分析第二个定时器:
- 第二个定时器调用箭头函数,按照上边的结论,箭头函数从定义它的最近的外层函数作用域中继承this,就是说箭头函数的this值继承了fn函数的this值。
- fn函数的this值是什么呢?fn函数这里是普通函数,且被obj对象所拥有,通过obj.fn()调用,属于隐式绑定,其this值就是obj对象。
- 我们将fn函数的this值保存在that变量中(为什么要这样做呢?任何内部函数都不能直接访问到外部函数的this对象)与箭头函数的this值比较,和预想的一样。箭头函数内部的this值等于外部fn函数的this值,也等于obj对象。
定时器中使用箭头函数
刚刚一通分析,看来定时器中传一个普通函数好像没啥用,this值一直指向window也不是个事。如果我想要用到上下文作用域中的变量怎么搞?使用箭头函数✌!
使用箭头函数的最大好处可能是在使用
setTimeout()和EventTarget.prototype.addEventListener()等方法时,这些方法通常需要某种闭包、call()、apply()或bind(),以确保函数在适当的作用域中执行。
示例1
对于传统的函数表达式,类似这样的代码并不能像预期的那样工作:
const obj = {
count: 10,
doSomethingLater() {
setTimeout(function () {
// 此函数在 window 作用域下执行
this.count++;
console.log(this.count);
}, 300);
},
};
obj.doSomethingLater(); // 输出“NaN”,因为“count”属性不在 window 作用域下。
有了箭头函数,this 作用域更容易被保留:
const obj = {
count: 10,
doSomethingLater() {
// 该方法语法将“this”与“obj”上下文绑定。
setTimeout(() => {
// 由于箭头函数没有自己的绑定,
// 而 setTimeout(作为函数调用)本身也不创建绑定,
// 因此使用了外部方法的“obj”上下文。
this.count++;
console.log(this.count);
}, 300);
},
};
obj.doSomethingLater(); // 输出 11
示例2:防抖
function handleNameSearch(e) {
console.log(this);
...
}
function debounce(func, delay) {
let timeoutId;
return function (...args) {
console.log(this);// inputb元素的引用
console.log(this === inputb);// true
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
// func.apply(this, args);
func(...args);
}, delay);
}
}
const debounceNameSearch = debounce(handleNameSearch, 500);
inputa.addEventListener('input', handleNameSearch);
inputb.addEventListener('input', debounceNameSearch);
- 先注释掉第10行的apply(), 使用第11行调用handleNameSearch函数。我们这里有两个输入框,第一个没有防抖效果,第二个防抖处理。分别触发一次输出handleNameSearch函数内的this值:分别输出了input元素的引用和window。很明显,处理防抖时把this搞丢了。
- 事件监听函数内部的this值是触发事件的元素的引用(第二部分讲),所以防抖函数返回的函数内部的this值就是元素的引用(代码9-10行已经输出验证了)
- 这样就好办了,我们现在要以这个this值来调用handleNameSearch: func.apply(this, args)
- 箭头函数的this值继承的刚好是我们想要的this。
- 这里要是使用普通函数也倒可以实现,可以将外层函数的this保存在一个变量里,这就相对比较麻烦了。
事件监听函数
事件监听函数中的this值
当使用 addEventListener() 为一个元素注册事件的时候,事件处理器里的 this值是该元素的引用。其与传递给句柄的 event 参数的 currentTarget 属性的值一样。当然,这只针对传统函数。
my_element.addEventListener("click", function (e) {
console.log(this.id); // 输出 my_element 的 id
console.log(e.currentTarget === this); // 输出 `true`
});
my_element.addEventListener("click", (e) => {
console.log(this.id); // 警告:`this` 并不指向 `my_element`
console.log(e.currentTarget === this); // 输出 `false`
});
总结
-
箭头函数的
this继承机制:- 箭头函数没有自己的
this上下文,它从定义它的最近的外层函数作用域中继承this。 - 箭头函数的
this值不会改变,即使在不同的上下文中调用,this仍然是定义它时所在作用域的this。
- 箭头函数没有自己的
-
定时器中的
this:- 在定时器中使用普通函数时,
this默认指向全局对象window(或在严格模式下为undefined)。 - 在定时器中使用箭头函数时,
this保留为定义箭头函数时的作用域的this,不会指向window,而是继承外层函数的this。
- 在定时器中使用普通函数时,
-
使用箭头函数解决
this问题:- 定时器和事件处理器中的
this往往会变成window或undefined。使用箭头函数可以避免this丢失的问题,它能够从定义时的上下文继承正确的this。 - 在需要保持上下文
this的情况下,箭头函数是一个很好的选择,避免了手动bind()或通过变量保存this的麻烦。
- 定时器和事件处理器中的
-
事件监听器中的
this:- 对于普通函数,事件处理器中的
this指向触发事件的 DOM 元素,与event.currentTarget一致。 - 使用箭头函数时,
this不再指向触发事件的元素,而是继承自定义时的外层作用域的this,因此不会指向event.currentTarget。
- 对于普通函数,事件处理器中的
-
常见的用法和场景:
- 使用箭头函数处理防抖和节流时,它继承外层的
this,可以更方便地操作元素或上下文对象。 - 在事件处理和定时器回调中,使用箭头函数能够确保
this指向正确的对象,避免意外指向全局对象window。
- 使用箭头函数处理防抖和节流时,它继承外层的