- JavaScript 中的闭包 ```javascript function createIncrement(i) { let value = 0; function increment() { value += i; console.log(value); } return increment; }
const inc = createIncrement(1); inc(); // 1 inc(); // 2
- 当在函数上返回一个函数时,有会有闭包产生。闭包捕获词法作用域中的变量 value 和 i。词法作用域是定义闭包的外部作用域。在本例中,increment() 的词法作用域是createIncrement()的作用域,其中包含变量 value 和 i。
- 闭包原理:闭包是基于正常的垃圾回收处理机制下的,也就是说,一般情况一个函数(函数作用域)执行完毕,里面声明的变量会全部释放,被垃圾回收器回收,但是有一些变量在函数执行完毕后还有可能被使用的话,就不会被垃圾回收器回收。闭包就是利用了一个技巧,让作用域里面的变量,在函数执行完之后还可以被使用。
2. JavaScript 中的过时闭包
```javascript
function createIncrement(i) {
let value = 0;
function increment() {
value += i;
console.log(value);
const message = `Current value is ${value}`;
return function logValue() {
console.log(message);
};
}
return increment;
}
const inc = createIncrement(1);
const log = inc(); // 打印 1
inc(); // 打印 2
inc(); // 打印 3
log(); // 打印 "Current value is 1"
- log()是过时的闭包。在第一次调用 inc() 时,闭包 log() 捕获了具有 “Current value is 1” 的 message 变量。而现在,当 value 已经是 3 时,message 变量已经过时了。
- 解决js中的过时闭包 ```javascript //解决过时闭包的第一种方法是找到捕获最新变量的闭包。找到捕获了最新 message 变量的闭包。就是从最后一次调用 inc() 返回的闭包。 const inc = createIncrement(1);
inc(); // 打印 1 inc(); // 打印 2 const log = inc(); // 打印 3 log(); // 打印 “Current value is 3”
4. useEffect() 中的过时闭包
```javascript
function WatchCount() {
const [count, setCount] = useState(0);
useEffect(function() {
setInterval(function log() {
console.log(`Count is: ${count}`);
}, 2000);
}, []);
return (
<div>
{count}
<button onClick={() => setCount(count + 1) }>
加1
</button>
</div>
);
}
//现象:单击几次加1按钮,然后看看控制台,每2秒打印 Count is: 0
//原因:第一次渲染时,状态变量count初始化为0。useEffect()调用setInterval(log, 2000)计时器函数,该计时器函数计划每2秒调用一次log()函数。 在这里,闭包log()捕获到count变量为0。之后,即使在单击Increase按钮时count增加,计时器函数每2秒调用一次的log(),使用count的值仍然是0。log()成为一个过时的闭包。
//源码层面:hook的源码中,每一个setCount执行后,会把当前的count丢到一个hook的链表上。每执行一次,就变成了hook.next = newhook这种感觉。setInterval每次打印的count都是时执行useEffect那个时候的count,实际上就是hook.next.count,后面再执行setCount只不过是往hook链表上新的{val:{count:x}}
解决useEffect()的过时闭包 ```javascript function WatchCount() { const [count, setCount] = useState(0);
useEffect(function() { const id = setInterval(function log() { console.log(
Count is: ${count}
); }, 2000); return function() { clearInterval(id); } }, [count]); return ({count}
<button onClick={() => setCount(count + 1) }>
Increase
</button>
```