JS 闭包
#
从 JS 中的变量说起JS 中的变量就像一个盒子,它里面可以装 true
、"字符串"
、666
、[1, { object: {} }, null]
等各类数据。
console.log
执行的时候,foo
这个箱子装的是{}
执行
箱子 foo
装的是{}
,在执行到 line:2 的时候,print 函数
绑定了箱子 foo
,但不会绑定箱子 foo
中装的东西
所以,变量跟变量所代表的值是两个东西,代码只有执行到使用变量的语句时,才会看这个变量箱子里装了什么。
#
关于闭包让我们从一个简单的例子开始,假设我们有一个类,叫 Person,需要有姓名、性别,且性别不可更改
在 JavaScript 还没有类、属性访问控制器的时候,我们要如何做到某个属性的访问控制呢?
在上面的例子中,createPerson
函数在执行时,有它自己的 FunctionScope(函数作用域)。return 的对象中有 4 个 函数:getName
、setName
、getGender
、setGender
。这四个函数又分别产生了自己的 FunctionScope。在 JS 中,函数作用域的变量是无法在外部被访问的,所以 _name
、_gender
外界无法直接读写,做到了访问控制。
当我们执行 getGender
方法时,由于 getGender
的 FunctionScope 自身没有 _gender
这个变量,所以会往父作用域查询是否有叫 _gender
的变量。
#
总结闭包的要点- 父作用域无法访问子作用域的变量,子作用域可以访问父作用域的变量。在函数声明时,有一个俗称绑定作用域的过程。可以简单理解为把所有父作用域的箱子都记住,之后在执行的时候,如果碰到名字叫 A 的箱子,就从离自己近的箱子开始找名称为 A 的箱子
- 每次函数执行时,都会创建一个全新的闭包对象,与上一次函数执行时的闭包对象完全独立
闭包作用域就是函数作用域+父作用域集合。闭包变量就是闭包作用域中的变量集合
对于上面函数 son
来讲,闭包变量有firstName=Son
、firstName=Daddy
、firstName=grandpa
、lastName=A
(从近到远排列,放入本函数作用域的参数表)。在访问 firstName 和 lastName 时,分别按照从近到远的方式查找。
继续阅读 可视化 v8 引擎管理内存 了解更多函数执行时的作用域、内存分配相关知识,加深对闭包的理解。
#
React 中常见的闭包场景React Hooks 的 API 设计存在大量闭包,追踪闭包变量是所有函数式编程的基础。
按 F12 打开 DevTools,查看 console 打印的 count1 是几
上面的代码由于 useEffect
函数的第二个传参是空数组,所以 useEffect
只会在首次渲染的时候执行。函数只执行一次,所以绑定的 count1 是首次渲染时的父函数作用域的 count1=0
。所以,console 打印的一直是 0。
让我们稍微修改一下代码:
count2 能正确更新到点击数,思考一下为什么?
当我们把 count 的作用域放到组件的外面时,每次组件函数执行时,绑定的父作用域的 count 没有改变,所以在修改之后,会影响到 console 的打印结果