大家好,我是前端学习小菜鸟 今天咱们聊点痛点:
为什么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 一下午。
我是小阳,感谢点赞收藏转发,咱们下篇见!