var|「建议收藏」词法作用域

var|「建议收藏」词法作用域

「建议收藏」词法作用域作用域可以视为一套规则 , 这套规则用来管理引擎如何在当前作用域以及嵌套的子作用域根据标识符名称进行变量查找 。
作用域共有两种主要的工作模型 , 第一种是最为普遍的 , 被大多数编程语言所采用的词法作用域 , 我们会对这种作用域进行深入讨论 。 另外一种叫做动态作用域 , 仍有一些编程语言在使用 。
词法阶段大多数标准化语言编译器的第一个工作阶段叫做词法化 , 词法化的过程会对源代码中的字符串进行检查 , 如果是有状态的解析过程 , 还会赋予单词意义 。
【var|「建议收藏」词法作用域】这个概念是理解词法作用域 , 及其名称来历的基础 。
简单的说 , 词法作用域就是定义在词法阶段的作用域 。 换句话说 , 词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的 , 因此当词法分析器处理代码时会保持作用域不变 。
function foo(a){
    var b = a * 2
    function bar(c){
        console.log(abc)
    
    bar(b*3)

foo(2)

在这个例子中有三个逐级嵌套的作用域 , 为了帮助理解 , 可以将它们想象成几个逐级包含的气泡 。


image-20220323180949187
?最外面的是整个全局作用域 , 其实只有一个标识符:foo 。 ?包含着 foo 所创建的作用域 , 其中有三个标识符: a、bar 和 b?包含着 bar 所创建的作用域 , 其中只有一个标识符: c
作用域气泡由其对应的作用域块代码写在哪里决定 , 它们是逐级包含的 , 每个函数都会创建一个新的作用域气泡 。
bar 的气泡完全被包在 foo 所创建的气泡中 , 唯一的原因是那里就是我们希望定义函数 bar 的位置 。
查找作用域气泡的结构和互相之间的位置关系 , 给引擎提供了足够的位置信息 , 引擎用这些信息查找标识符的位置 。
在上一个代码片段中 , 引擎执行 console.log 声明 , 并查找 a、b 和 c 三个变量的引用 。 它首先从最内部的作用域 , 也就是 bar 函数的作用域气泡开始查找 。 引擎无法在这里找到 a , 因此会去上一层所嵌套的 foo 的作用域中继续查找 , 在这里找到了 a , 因此引擎使用了这个引用 。 对 b 来说也是一样的 。 而对 c 来说 , 引擎在 bar 中就找到了它 。
如果 a、c 都存在于 bar 和 foo 的内部 , console.log 就可以直接使用 bar 中的变量 , 而无需去 foo 中查找 。
作用域查找会在找到第一个匹配的标识符时停止 。 在多层嵌套作用域中可以定义同名的标识符 , 这叫做「遮蔽效应」 , 内部的标识符遮蔽了外部的标识符 。 抛开遮蔽效应 , 作用域查找始终从运行时所处的最内部作用域开始 , 逐级向外或者说向上进行 , 直到遇见第一个匹配的标识符为止 。
无论函数在哪里被调用 , 也无论如何被调用 , 它的词法作用域都只由函数被声明时所处的位置决定 。

注:这点很重要 , 这对传递参数提供了无数可能 。
词法作用域只会查找一级标识符 , 比如 a、b 和 c 。 如果代码中引用了 foo.bar.baz , 词法作用域查找只会试图查找 foo 标识符 , 找到这个变量后 , 对象属性访问规则会分别接管 bar 和 baz 属性的访问 。
欺骗词法如果词法作用域完全由写代码期间函数所声明的位置来定义 , 怎样才能在运行时来修改词法作用域呢?
JavaScript 有两种机制来实现这个目的 。 社区普遍认为在代码中使用这两种机制并不是什么好主意 , 因为:欺骗词法作用域会导致性能下降 。
在了解性能问题之前 , 先来看看这两种机制分别是什么原理 。
evalJavaScript 的 eval 函数可以接收一个字符串为参数 , 并将其中的内容视为好像在书写时就存在于程序中这个位置的代码 。 换句话说 , 可以在你写的代码中用程序生成代码并运行 , 就好像是写在那个位置一样 。
根据这个原理来理解 eval , 它是如何通过代码欺骗和假装书写时代码就在那儿 , 来实现修改词法作用域环境的?
在执行 eval 之后的代码时 , 引擎并不「知道」前面的代码是以动态的形式插入进来的 , 引擎只会如往常地进行词法作用域查找 。
function foo(stra){