你不知道的闭包
学东西一定要学习原理,搞清楚Why的问题。
- 首先请您自己在纸上描述下闭包是什么?
- 作用域是什么?
- 是不是可以描述清楚? ####闭包一个JS神秘的存在,每个开发者都在使用,人人都以为自己掌握了,只是很少有人可以明明白白的描述清楚它的定义。
代码示例参考文章,个人建议看完这两篇文章再继续往下阅读(至少看下第一篇MDN的示例)。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closureshttp://www.jointforce.com/jfperiodical/article/3273?utm_source=tuicool&utm_medium=referral
一、闭包个人理解
- 闭包是子函数对父函数作用域的持续引用。
- 学术上的闭包和我们通常使用的闭包是两个概念。
- 学术上闭包:一个函数作用域包含另一个函数作用域就会形成闭包。
- 我们使用的闭包(比较直观):内部函数在词法作用域以外被引用,它支持有原始词法作用域的引用。闭包由它本身和它的环境组成。当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产了闭包。 定时器、 事件 监听器、 Ajax 请求、 跨 窗口 通信、 Web Workers 或者 任何 其他 的 异步( 或者 同步) 任务 中, 只要 使 用了 回 调 函数, 实际上 就是 在 使用 闭 包!因为回调函数会引用定义它的词法作用域中的变量和函数对象等。
- 闭包也可以描述为那些能够访问独立(自由)变量的函数 (变量在本地使用,但定义在一个封闭的作用域中)。换句话说,这些函数可以“记忆”它被创建时候的环境。
- 典型情况:1、一个函数中定义了可以返回的函数。2、onClick、onFocus、Eventlisener都是闭包(在它们中可以访问绑定事件的作用域变量)
- 闭包由函数本身和创建它的环境(词法作用域)组成。它的环境由它自身的scope属性指向。
- 可以用闭包去生成容器,解决多个图表异步刷新的问题,因为通过闭包可以保留上下文环境。
二、作用域的个人理解
- 作用域:变量存储和查询的规则,规定了代码对变量的访问权限。
- 词法作用域:在编译器读取变量和函数声明时产生的。
- 动态作用域:this机制在函数调用的过程中产生的。
- 函数和变量都是有自己的作用域的,子作用域的变量无法从父作用域中进行访问。
- 引擎会对变量从当前作用域对变量一直向上寻找,如果找不到继续向上寻找,直到找到全局作用域为止。JS使用的是词法作用域,函数的词法作用域由它所处的位置决定。
- JS的函数模块管理器也是基于作用域来实现的。
- ES6下新模块管理机制:export将当前模板(文件)作为变量导出为公共API。import将模块中的API变量导入到当前引用的作用域中。编译器在( 的 确 也 这样 做了) 编译 期 检查 对 导入 模块 的 API 成员 的 引用 是否 真实 存在。 如果 API 引用 并不 存在, 编译器 会在 运行时 抛出 一个 或 多个“ 早期” 错误, 而 不会 像 往常 一样 在 运行 期 采用 动态 的 解决 方案。
三、JS的编译执行过程
javascript是一门动态编译语言。JS编译器在编译过程中生成作用域规则,然后浏览器引擎根据作用域的定义和其它程序语法执行机器码。js引擎没有像其他引擎那样有预编译的过程(优化),它通常在执行前的几毫秒甚至更少的时间内进行编译(不像C#、Java)。所以用了很多黑科技比如JIT, lazy compile hot re-compile.
JS编译过程:
- 解析/语法分析:根据程序语法结构树对程序进行语法分析,生成AST(将 词法 单元 流( 数组)转换成一个由素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“ 抽象语法树” Abstract Syntax Tree, AST)。
-
代码生成:将AST转换为可执行的机器代码
- function foo( a) { console. log( a ); // 2 } foo( 2 ); 这段代码JS引擎是怎么处理的。引擎 和 作用域 的 对话如下。
- 引擎:我说作用域, 我 需要 为 foo 进行 RHS 引用。(LHS:有了值,找到赋值操作的目标变量或者说是容器。RHS:有了变量,谁是赋值操作的源头或者说变量的值来自哪里。)你 见过 它 吗?
- 作用域: 别说, 我还 真 见过, 编译器 那 小子 刚刚 声 明了 它。 它是 一个 函数, 给你。
- 引擎: 哥们 太 够 意思 了! 好吧, 我来 执行 一下 foo。
- 引擎: 作用域, 还有 个 事儿。 我 需要 为 a 进行 LHS 引用, 这个 你 见过 吗?
- 作用域: 这个 也 见过, 编译器 最近 把 它 声名 为 foo 的 一个 形式 参数 了, 拿去 吧。
- 引擎: 大恩 不言 谢, 你 总是 这么 棒。 现在 我 要把 2 赋值 给 a。
- 引擎: 哥们, 不好意思 又来 打扰 你。 我要 为 console 进行 RHS 引用, 你 见过 它 吗?
- 作用域: 咱俩 谁 跟 谁 啊, 再说 我 就是 干 这个 的。 这个 我 也有, console 是个 内置 对象。 给你。
- 引擎: 么 么 哒。 我得 看看 这 里面 是不是 有 log(..)。 太 好了, 找到了, 是一 个 函数。
- 引擎: 哥们, 能 帮我 再找 一下 对 a 的 RHS 引用 吗? 虽然 我 记得 它, 但 想 再确认 一次。
- 作用域: 放心 吧, 这个 变量 没有 变动 过, 拿走, 不谢。
- 引擎: 真棒。 我来 把 a 的 值, 也就是 2, 传递 进 log(..)。