大家好,我是前端学习小菜鸟 今天咱们聊点痛点:
为什么this
总丢?
箭头函数凭啥能保住this
?
bind
、that = this
真的不是土办法?
节流函数里that
又是怎么混进闭包的?
别急,一起带着问题,撸个示例,一把梳理明白!
1. 谁偷走了 this
?
先看下面这段:
js
const person = {
name: 'Allen',
sayHello: function() {
setTimeout(function() {
console.log(`${this.name} says hello!`);
}, 1000)
}
}
person.sayHello(); // ???
你以为 1 秒后能打印 "Allen says hello!" ,结果呢?
输出是 " says hello!"
或报 undefined
,this
丢了。
为啥丢?
setTimeout
的回调函数,是普通函数调用 ,所以this
默认是window
(非严格模式)。this.name
就找不到person.name
了,GG。
解法 1:保存 that = this
js
const person = {
name: 'Allen',
sayHello: function() {
let that = this;
setTimeout(function() {
console.log(`${that.name} says hello!`);
}, 1000)
}
}
that
把外层的 this
(就是 person
)保存下来,闭包里能用,老土但好用!
解法 2:用 bind
改变 this
js
const person = {
name: 'Allen',
sayHello: function() {
setTimeout(function() {
console.log(`${this.name} says hello!`);
}.bind(this), 1000)
}
}
bind(this)
返回一个新的函数,this
永远固定成 person
,优雅且无副作用。
解法 3:用箭头函数
js
const person = {
name: 'Allen',
sayHello: function() {
setTimeout(() => {
console.log(`${this.name} says hello!`);
}, 1000)
}
}
箭头函数没有自己的 this
!
它的 this
自动取外层 sayHello
的 this
,就是 person
,完美继承。
总结:this
保不住?要么 that
,要么 bind
,要么 =>
!
2. throttle
节流背后,其实也是闭包+that
!
很多同学学节流、去抄百度出来的版本,只会用 throttle
,却没想过:为啥 throttle
函数里总要写个 that = this
?
看下面这段节流函数
ini
function throttle(fn, delay) {
let last, deferTimer;
return function(args) {
let that = this; // 保存外层 this!
let now = +new Date();
if (last && now < last + delay) {
clearTimeout(deferTimer);
deferTimer = setTimeout(function() {
last = now;
fn.call(that, args); // 用 call 保住 this
}, delay);
} else {
last = now;
fn.call(that, args);
}
}
}
这里面发生了啥?
1️⃣ throttle
返回一个新函数,叫包装函数 ,这就是闭包 !
2️⃣ that = this
把外层(事件监听器触发时)的 this
保存下来。
3️⃣ 真正执行 fn
时,用 fn.call(that, args)
确保 this
没跑掉。
如果你把 that
去掉,直接写:
js
fn(args)
那里面 this
就是 window
(或 undefined
),又炸了。
为啥需要 this
?
想想:
jsx
inputC.addEventListener('keyup', function(e) {
throttleAjax(e.target.value);
});
这个匿名函数里的 this
是 inputC
(事件触发元素),所以包装后的 throttle
执行 ajax
时,也要保证 ajax
里的 this
正确传递。
3. 节流应用:输入防抖和节流
有了节流,输入框也不怕狂打字了
jsx
let throttleAjax = throttle(ajax, 200);
inputC.addEventListener('keyup', function(e) {
throttleAjax(e.target.value);
});
4. 所有这些,背后都是闭包!
throttle
用闭包保存last
和deferTimer
。that = this
利用闭包保存外层上下文。- 箭头函数也是闭包 + 继承上下文。
bind
其实是生成一个新的闭包,预先把this
固定住。
一句话总结
闭包保护变量,that
和 bind
固定 this
,箭头函数天然"偷懒"省事!
彩蛋:立即执行函数(IIFE)也是闭包!
再看闭包经典套路:
js
const Counter = (function() {
let count = 0;
return function() {
return {
increment: () => ++count,
getCount: () => count
}
}
})();
每次 Counter()
,就开一套闭包,count
外面拿不到,但接口能访问,私有变量 + 公有方法,经典组合拳。
🎉 全文小结
技术 | 用法 | 干嘛用 |
---|---|---|
闭包 | 保存变量 | count 、last 、deferTimer |
that = this |
保存上下文 this |
保证回调里 this 不丢 |
bind |
固定 this |
返回新函数,this 永远不跑 |
箭头函数 | 没有 this |
自动用外层的 this |
IIFE | 私有作用域 | 立即执行,封装变量 |
写给自己也写给未来的同事
这套小技巧面试也考、项目也用,别背死了,搞懂原理,下次再遇到 this
丢了,淡定写个箭头函数,没法用就 bind
,再不行就 that = this
,保证老板再也看不到你 Debug 一下午。
我是小阳,感谢点赞收藏转发,咱们下篇见!