简单聊一聊闭包

2023-08-23 0 333

前段时间根据许多讲义试著实现两个固定式的 react-hooks. 里头加进了大批的旋量群, 翻看了许多该文加之许多自己的思索, 重新整理呵呵

这首诗更多的是想谈谈为甚么会用旋量群, 和我们能用旋量群来做些甚么. 许多网路上的讲义只是讲呵呵甚么是旋量群(许多甚至都没有讲知道), 而对于旋量群的前述应用领域常常是推而广之, 比如说chan上有那么两个问题:Python 简而言之的“旋量群”是并非秉持蓄意把人搞晕的立场发明者出来的?

生前也是新手, 可能许多地方性认知的也并非很精确, 也热烈欢迎和我沟通交流

从表达式的开发周期讲起

表达式也是能看作有开发周期的, 如下表所示图:

简单聊一聊闭包

具体内容能参照这篇该文, 这儿就不多叙述了

甚么是旋量群

网路上看到两个比较简化的说明:

两个表达式在初始化的这时候, 外部的民主自由表达式, 要到那个表达式被表述的地方性去找, 而并非在那个表达式现阶段被初始化的地方性去找 那个表达式与及它被表述时的自然环境一起, 形成了两个计算机程序, 就是旋量群

或者说想去介绍旋量群的整个继续执行业务流程和基本概念, 我所推荐看这篇

讲的十分精辟, 从语法结构自然环境, 讲到返回值链和活动第一类, 是十分明晰的.

不过这儿还是归纳呵呵, 有那么以下几点:

每一表达式建立的这时候, 就已经会建立两个语法结构自然环境. 当在运转的这时候, 建立两个捷伊语法结构自然环境, 这两个语法结构自然环境很有可能是不同的. 所以分析的这时候一定是要看最捷伊语法结构自然环境, 比如说如下表所示的范例:
let phrase = Hello function say(name) { alert(`${phrase}, ${name}`) } say(John) // Hello, John
简单聊一聊闭包
表达式say建立时的语法结构自然环境
简单聊一聊闭包
表达式say()初始化时的语法结构自然环境

所以能看出来, 建立时和运转时的语法结构自然环境是截然不同的, 同时由于引加进了全局自然环境下的phrase表达式, 如果在say()初始化前修改该表达式, 那么初始化say()的这时候, 其语法结构自然环境又会发生变化, 比如说将代码改成如下表所示形式:

let phrase = Hello function say(name) { alert(`${phrase}, ${name}`) } phrase = World say(John) // World, John

很明显的, 在初始化say(John)那个表达式的这时候, 其语法结构自然环境中引用的phrase表达式已经被修改, 因此最后结果为 “World, John”

当然如果将phrase的修改放到表达式继续执行结束后, 那么语法结构自然环境并不会改变, 毕竟表达式的修改是在表达式结束之后发生的, 在继续执行say()表达式的这时候, 其语法结构自然环境中的phrase并没有被修改掉, 代码如下表所示:
let phrase = Hello function say(name) { alert(`${phrase}, ${name}`) } say(John) // Hello, John phrase = World

所以归纳来说, 一定要去看表达式被初始化时的语法结构自然环境, 由于语法结构自然环境里头常常引加进了外部的表达式, 自然环境等, 很有可能在被初始化时已经和建立的这时候发生了很大的变化(例如上面的say表达式范例)

语法结构自然环境包括自己外部的自然环境, 和引用的外部的自然环境. 当存在嵌套表达式的这时候, 外部自然环境可能还引用更加外部的自然环境, 就形成了一条返回值链. 最最里层的表达式在继续执行的这时候, 会根据这条链子, 由内而外寻找需要的表达式. 然后能对找到的表达式进行修改, 修改是在该表达式所在的返回值内修改, 也就是说对于旋量群来讲, 修改的地方性在该表达式的外部自然环境修改, 而非克隆一份放低自己的外部自然环境内修改. 比如说下表所示面代码:
function makeCounter() { let count = 0 return function() { return count++ } } let counter = makeCounter() alert(counter())
简单聊一聊闭包
继续执行旋量群, 建立对外部自然环境的引用
简单聊一聊闭包
修改外部自然环境表达式 每次初始化两个表达式, 如果那个表达式存在旋量群, 那么都会建立两个单独的旋量群自然环境, 里头有该旋量群的状态. 多次初始化那个表达式, 会建立多个旋量群, 这些旋量群外部的自然环境状态都是独立的. 类似于有两个类, 你能进行多次实例化, 每次实例化出来的都是不同的第一类. 例如下表所示面的范例:
function makeCounter() { let count = 0 return function() { return count++ } } let counter1 = makeCounter(); let counter2 = makeCounter(); alert(counter1()) // 0 alert(counter1()) // 1 alert(counter2()) // 0 (独立的)

如果想深入介绍整座的业务流程还是需要去看上面所推荐的那篇该文, 也有英文版的. 另提一句, 那个讲义的其他内容质量也是不错的, 也有对应的中文翻译, 能参照

旋量群的几个小练习

两道旋量群的小练习题:

第一题

function foo() { let x = 100 function add(_x) { x += _x console.log(change closure x, x) } return [x, add] } const [x1, add1] = foo() console.log(x1 before, x1) // x1 before, 100 add1(5) // change closure x, 105console.log(x1 after, x1) // x1 after, 100 const [x2, add2] = foo() console.log(x2 before, x2) // x2 before, 100add2(10) // change closure x, 110 console.log(x2 after, x2) // x2 after, 100

这儿有两个点需要注意:

每次继续执行表达式以后建立的旋量群其自然环境都是独立的 引用问题, 再初始化完add1()表达式以后, x已经改变了地址, 不再指向 100, 而是指向了 105, 然而x1还是指向原始的 100, 不认知的能看下面的示例代码:
let a = 0 let b = a // a 和 b 指向同一地址 a = 100 // a 的指向改变, b 不变 console.log({ a, b }) // { a: 100, b: 0 }

第二题

function foo() { let x = 100 function render() { const _x = x function inner() { x += 5 console.log(innerrender, { _x, x }) } console.log(render, { _x, x }) return inner } function clear() { x = 0 } return [add, render, clear] } const [add, render, clear] = foo() inner = render() // render { _x: 100, x: 100 } clear() inner() // innerrender { _x: 100, x: 5 }

这儿存在多个嵌套的旋量群, 在初始化最里层的表达式inner()的这时候需要分析清楚此时它所在的自然环境里引用的x和_x的值是怎样的

旋量群的应用领域

上面花了很大的篇幅将旋量群是甚么, 这儿谈谈旋量群的前述应用领域

在之前, 先归纳呵呵旋量群的几个特点:

使得表达式有状态, 且状态能改变 使得表达式有记忆 状态是私有的, 能对外暴露方法读取/修改它 每一旋量群里头的自然环境都是独立, 多次初始化同两个表达式创造出来的多个旋量群的自然环境都是相互隔离的

能想到, 普通(纯)表达式是没有状态的, 旋量群能使得表达式有状态, 有状态就意味着有记忆, 毕竟状态能发生改变. 当然, 这儿的状态, 常常是在外部自然环境所提供的, 不过外部表达式能读取并操作那个状态.

所以…如果你想进行许多状态的管理与保存, 你前述上能有两个选择:

用类 用旋量群 旋量群和类都是保存状态用的

如果熟悉React, 类和旋量群好像有点 class 组件 和 React hooks 的味道!

不过这儿先不讲React, 先考虑如下表所示范例, 想实现两个计数器, 有那么两个功能:

能得到现阶段的计数 能增加现阶段的计数

假设我们想会用普通纯表达式实现, 可能会那么写:

const Counter = function() { let count = 0 count++ return count } Counter() // 1 Counter() // 1

这儿的表达式是没有状态的, 初始化多少次永远都是 1. 同时外部表达式count在表达式初始化完毕之后也被垃圾回收了. 所以那个计数器的实现是失败的

那如果用类实现, 可能代码是这样:

class Counter { constructor() { this.count = 0 } add() { this.count += 1 } getCount() { return this.count } } c1 = new Counter() c1.add() c1.add() c1.getCount() // 2 c2 = new Counter() c2.add() c2.getCount() // 1

如果用旋量群来实现:

const Counter = function() { let count = 0 const add = function() { count += 1 } const getCount = function() { return count } // return { getCount, add } return [getCount, add] // 返回两个数组或者第一类都能 } const c1 = Counter() c1[1]() c1[1]() c1[0]() // 2 const c2 = Counter() c2[1]() c2[0]() // 1

使用旋量群模拟, 每次运转Counter()产生的旋量群自然环境都是独立的, 不受其他旋量群影响. 注意这儿的旋量群指的是getCount和add两个旋量群, Counter只是提供这两个旋量群的一部分自然环境(count), count作为状态, 被这两个表达式读取操作

通过以上范例, 我们用旋量群, 实现了类才能实现的功能, 这是一件十分神奇的事情.

旋量群和 React Hooks

当然, 旋量群的前述应用领域还有许多, 比如说React Hooks, 写这首诗的原因很大程度上也是因为Hooks的使用还是有许多心智负担的, 想更好的使用还是需要去介绍许多稍微底层的原理.

如果你对 React-hooks 的实现比较感兴趣, 能参照这首诗, 这首诗使用原生JavaScript实现了最最单纯的Hooks. 不过还是有许多地方性说的比较简略. 后续可能会再写一篇博客将里头代码做更加详细的分析

如果对 React 或者 hooks 不介绍的, 我十分所推荐去看一看 Redux 作者 Dan Abramov 在 2018 年 React Conf 上关于 hooks 介绍的一篇演讲:

相信你会十分震撼的.

另: chan的 markdown 导入真的糟糕…为了更加良好的阅读体验能去我的博客看

参照

https://learnku.com/articles/5304/graphical-javascript-closurehttps://zh.javascript.info/closurehttps://www.zhihu.com/question/34872127https://www.netlify.com/blog/2019/03/11/deep-dive-how-do-react-hooks-really-work/https://www.youtube.com/watch?v=dpw9EHDh2bM&t=2281s

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务