什么是闭包
函数 + 作用域
垃圾回收
参考: Garbage collection (javascript.info)
索引切换导致的垃圾回收
let fiber = { type: 'div' }
fiber = null // 此时 { type: 'div' } 会被垃圾回收
函数执行
- 当函数执行时
let a被创建 - 当函数执行完毕,
let a被垃圾回收
function fn() {
let a = 1
}
此时let a不会被垃圾回收
function fn() {
let a = 1
return b => a = a+b
}
const add = fn() // 此时变量a将无法被垃圾回收
标记清除算法
- 全局变量不会被垃圾回收
- 和全局变量有关系的会被打上勾
- 没打勾的会被垃圾回收
新生代
- 垃圾回收发生得相对频繁
- 新生代主要用于存放新创建的对象。
- 由于大多数对象在创建后不久就变得不再需要,
- 因此新生代中的垃圾回收发生得相对频繁。
旧生代
- 旧生代主要用于存放存活时间较长的对象。
- 这些对象在经过多次新生代垃圾回收后仍然存活(全局变量的关系户)
- 因此被移动到旧生代。
- 旧生代的垃圾回收频率通常较低,
- 因为其中的对象生命周期较长。
- 当旧生代的内存空间不足时,
- 会触发Major GC(老年代垃圾回收)来回收不再使用的对象。
内存泄漏
满足内存泄漏的两个条件
- 这个变量没有用了
- 这个变量无法被垃圾回收
内存泄漏的场景1
被全局引用, 组件卸载时未及时清理
- 被全局变量引用
- 被全局函数使用
function App() {
const [arr, setArr] = useState([]);
useEffect(() => {
// 下面两种情况都会导致内存泄漏
window.arr = arr
window.fn = () => console.log(arr)
}, []);
return <h1>app</h1>;
}
- 改进
function App() {
const [arr, setArr] = useState([]);
useEffect(() => {
window.arr = arr
window.fn = () => console.log(arr)
return () => {
window.arr = null
window.fn = null
}
}, []);
return <h1>app</h1>;
}
内存泄漏的场景2
被定时器使用, 组件卸载时未及时清理
function App() {
const [arr, setArr] = useState([]);
useEffect(() => {
setInterval(() => {
console.log(arr);
}, 1000);
}, []);
return <h1>app</h1>;
}
改进
function App() {
const [arr, setArr] = useState([]);
useEffect(() => {
const timeId = setInterval(() => {
console.log(arr);
}, 1000);
return () => clearInterval(timeId);
}, []);
return <h1>app</h1>;
}
内存泄漏的场景3
注册事件, 组件卸载时未及时清理
function App() {
const [arr, setArr] = useState([]);
useEffect(() => {
const print = () => console.log(arr)
window.addEventListener('resize', print)
}, []);
return <h1>app</h1>;
}
改进
function App() {
const [arr, setArr] = useState([]);
useEffect(() => {
const print = () => console.log(arr)
window.addEventListener('resize', print)
return window.removeEventListener('resize', print)
}, []);
return <h1>app</h1>;
}
闭包不会导致内存泄漏
- 如果我们将闭包的相关依赖设置为
null - 就能触发垃圾回收
- 但是由于相关依赖太多(一千多个), 造成一部分依赖被遗漏
- 那么这个闭包就无法被垃圾回收掉
const cleanUp = new FinalizationRegistry((key) => {
console.log('出发垃圾回收:', key)
})
const createFn = () => {
let o = {a: 1}
cleanUp.register(o, '变量o被垃圾回收了')
return () => {
return o
}
}
let fn = createFn() // fn使用到了o, 导致o无法被垃圾回收
let b = fn() // 全局变量b指向o, 导致o无法被垃圾回收
document.addEventListener('click', () => {
// 斩断o的一切联系, 将o垃圾回收掉
fn = null
b = null
})
闭包: 无内存泄漏
- 变量
n一直是有用的, 不存在什么内存泄漏
function createAdd() {
let count = 0
return () => {
return count++
}
}
const add = createAdd()
const click = () => {
const n = add()
console.log(n)
}
window.addEventListenner('click', click)
闭包: 容易造成内存不足
- 下面代码由于忘记将相关依赖设置为null, 导致闭包无法被垃圾回收
- 点击三次后, 变量count已经没有存在的必要了
- 应该将变量count垃圾回收掉
- 但变量count不会自动被垃圾回收
- 如果不将相关依赖设置成
null - 变量count永远不会被垃圾回收
function createAdd() {
let count = 0
return () => {
return count++
}
}
// 函数add使用到了变量count, 导致count无法被垃圾回收
// 点击三下后, 函数add就没有用了
// 需要我们手动触发垃圾回收, 不然就是内存泄漏
const add = createAdd()
const click = () => {
const n = add()
console.log(n)
if(n>3) {
window.removeEventListenner('click')
}
}
window.addEventListenner('click', click)
将上面代码优化, 避免内存泄漏
function createAdd() {
let count = 0
return () => {
return count++
}
}
const add = createAdd()
const click = () => {
const n = add()
console.log(n)
if(n>3) {
window.removeEventListenner('click')
add = null
}
}
window.addEventListenner('click', click)
形成闭包的场景
{
var count
windown.add = () => count++
}
let fn
{
let a = 1
fn = () => a
}
const createFn = () => {
let n = 1
return () => {
return n
}
}
- 虽然是闭包,
- 但是立即函数执行完成后
- count会被垃圾回收掉
(function fn(){
var count = 0
return ()=> count++
})()
闭包的应用
useEffect
为什么 useEffect 能打印出 n 变化前的值
const [n, setN] = useState(0)
useEffect(() => {
console.log(n, '------ after');
return () => console.log(n, '-------- before'); // 打印变化前的值
})
useEffect原理
let before: Function = null
function useEffect(after: Function) {
if (!before) before = after()
before()
before = after()
}
解释
let before: Function = null
// 第一次执行
{
const n = 0
const after = () => {
console.log(n)
return () => console.log(n)
}
before = after() // 打印0
before() // 打印0
before = after() // 打印0
}
// 第二次执行
{
const n = 1
const after = () => {
console.log(n)
return () => console.log(n)
}
before() // 打印: 0, 这是第一次执行after返回的函数
before = after() // 打印: 1
}
// 第三次执行
{
const n = 2
const after = () => {
console.log(n)
return () => console.log(n)
}
before()
before = after()
}