大家好,我是江湖不渡i,前端菜鸡,react初学者。
今天依旧是学习react的一天,在看视频的过程中看到这样的一个问题:
const test = () => {
let num = 0;
const effect = () => {
num += 1;
const massage = `现在的num的值:${num}`;
return function unmount() {
console.log(massage)
}
}
return effect;
}
const add = test();
const unmount = add();
add();
add();
unmount();
各位大佬觉得应该输出什么?😂
反正我当时看完心想这不是闭包吗?add函数走了三次,num累加了三次,肯定输出3呀!😎
不过等我一运行,打脸了😭
静下心来看看这是为什么?先逐行分析一下代码
const test = () => {
let num = 0;
const effect = () => {
num += 1;
const massage = `现在的num的值:${num}`;
return function unmount() {
console.log(massage)
}
}
return effect;
};
// 执行test函数,返回effect函数
const add = test();
// 执行effect函数,返回引用massage的unmount函数
const unmount = add();
// 执行effect函数,返回 ???
add();
add();
unmount();
分析到这一行的时候,我想这不是同一个函数吗?const怎么定义了三次同名变量没有报错?什么意思?🤔️
这时候我已经忘记这个问题的本来面目,一心想知道const为啥定义三次同名没有报错?
我想虽然执行了三次add(),但是每到下一次执行的时候上一次执行的函数作用域已经被销毁了,所以每次创建的massage变量都是新的。但是这段代码的执行结果还有unmount函数里面对massage的引用会阻止变量被回收(也就是闭包可能造成内存泄露的原因)都让这个想法不成立。
那到底是因为什么?😵💫
然后我去看了看作用域链以及es6对块级作用域的说明,我觉得执行了三次add(),虽然看似是同一个函数执行了三次,但是结果是产生了三个不同的函数作用域,在作用域链上理解就是三次执行生成的作用域对象虽然内容相同但是对应的是不同的内存地址(感觉这样稍微有点牵强),所以三次执行所声明的massage所处的块级作用域是不同的,这样为什么const定义三次同名没有报错的问题就有了比较合理的解释。
回到最初的问题,为什么打印是1?根据上面的理解也可以得出合理的推测:
const test = () => {
let num = 0;
const effect = () => {
num += 1;
const massage = `现在的num的值:${num}`;
return function unmount() {
console.log(massage)
}
}
return effect;
};
// 执行test函数,返回effect函数
const add = test();
// 执行effect函数,返回引用massage1的unmount函数
const unmount = add();
// 执行effect函数,返回引用massage2的unmount函数
add();
// 执行effect函数,返回引用massage3的unmount函数
add();
unmount();
因为unmount同样是一个闭包,所以massage1在定义的时候保存的是"现在的num的值:1",后续也没有修改。
到现在这个闭包的问题就有了一个合理的解释,至于react中遇到这种问题的场景,简单类似下面的这样:
const Test = () => {
const [num, setNum] = useState(0);
const alertFun = () => {
setTimeout(() => {
alert(num);
}, 1000);
};
return (
<div>
<div>value: {num}</div>
<button onClick={() => setNum(num + 1)}>add</button>
<button onClick={alertFun}>alert</button>
</div>
)
}
点击alert按钮之后,多次点击add按钮,一秒之后弹框中num是1,这是因为useState声明的num每次发生改变都会导致这个函数组件重新render,而函数组件每次重新render都会生成一个新的alertFun函数,每个新函数里面引用的都是当时的num,就是产生了一个引用当前num的闭包。
至于怎么解决这样的问题,看了官方hooks的文档,useRef可以解决这样的问题。关键就在于useRef会在每次渲染时返回同一个ref对象。所以不管组件渲染多少次,我们通过useRef().current修改的都是同一个内存的值。
const Test = () => {
const [num, setNum] = useState(0);
const newNum = useRef(num);
useEffect(() => { newNum.current = num }, [num])
const alertFun = useCallback(
() => {
setTimeout(() => {
alert(newNum.current)
}, 1000);
},
[value]
);
return (
<div>
<div>value: {num}</div>
<button onClick={() => setNum(num + 1)}>add</button>
<button onClick={alertFun}>alert</button>
</div>
)
}
这个问题也可以做一些延伸:
// 第一种
const test = () => {
let num = 0;
let massage = '';
const effect = () => {
num += 1;
massage = `现在的num的值:${num}`;
return function unmount() {
console.log(massage)
}
}
return effect;
};
const add = test();
const unmount = add();
add();
add();
unmount();
//第二种:
// 这种其实能证明上面的说法
const test = () => {
let num = 0;
const effect = () => {
num += 1;
const massage = `现在的num的值:${num}`;
return function unmount() {
console.log(massage)
}
}
return effect;
};
const add = test();
const unmount = add();
const unmount1 = add();
add();
unmount();
unmount1();
最后祝各位大佬学习进步,事业有成!🎆