夜下客

繁体版 简体版
夜下客 > JS修炼法则 > 第6章 JS作用域(链)

第6章 JS作用域(链)

作用域(链)

引子: 前面讲述的变量和函数,那么他们存储在哪里?程序执行时如何找到他们?

1. 作用域

1-1. 定义

程序源代码中定义变量的区域。(存储和查找变量的机制 叫做作用域)

JS 采用词法作用域(静态作用域),作用域在函数定义的时候决定。所以我们也可以称作用域为词法作用域(lexical environment)。 表示变量的可用范围,代码执行开辟栈内存空间。

1-2. 作用

规定了如何查找变量,避免了不同范围的变量互相干扰。

1-3. 静态作用域分类(2 类 + 延伸)

(1)全局作用域 优点:变量可重复使用,随处可用 缺点:全局污染

(2)函数作用域(局部作用域) 优点:不污染全局 缺点:不可以重复使用(因函数执行完毕会 GC),仅可以在函数内部使用。

延伸: ES6 块作用域

需要搭配 let 、 const。 let 可以产生块作用域的搭配:for、if、switch、try/catch/finally。

1-4. V8 解析

V8解析器在解析时遇到函数声明,会调用预解析器对该函数进行一次快速预解析,检查语法错误和是否引用外部变量。如果该函数引用了外部变量,则将栈中的变量复制到堆中。反之,会跳过函数内部的代码,不会为函数体生成 AST 和字节码,仅生成顶层 AST 和字节码。

预解析器是 惰性解析 解决闭包所带来的问题的手段。

1-5. C++源码

作用域会维护一个variables_ 表,每次声明 V8 都会挂载一个declaration节点。 对于一个变量有多个declaration节点,会影响查找性能,所以应该避免重复声明。

1-6. 隐藏知识点

1-6-1. 隐藏变量 this

作用域生成时,系统会添加一个隐藏变量 this。这是在 ES2018 规范中定义的,将 this 纳入 lexical environment(词法环境),在 ES3 和 ES5 中都是规范了 this value 来表示 this。

1-6-2. 具名函数表达式的作用域

var aj = '大三'

;(function aj() {

aj = '毕业'

console.log(aj) // [Function: aj]

})()

console.log(aj) // '大三'

上述代码我们知道会产生至少两个作用域,当前 b 函数执行作用域-->全局作用域。 但具有名称的函数表达式很特殊,它会在两者中间产生一个特殊的作用域,该作用域中只有只读变量名为 函数名和值为函数自身。故当前作用域链为 当前 b 函数执行作用域-->具名作用域-->全局作用域。 所以当执行到 IIFE 中时,试图将只读变量aj:f修改为aj:'毕业',显然该语句执行无效。console.log 输出为具名作用域的变量值。函数执行完毕,函数作用域和具名函数作用域销毁,不影响全局作用域。

2. 作用域链

2-1. 定义

当查找变量时,会先从当前上下文的变量中查找,如果没有找到,会从父级执行上下文的变量对象中查找,一直找到全局上下文的变量对象。这样由多个执行上下文的变量对象构成的链表就是作用域链。

遮蔽效应

局部作用域的变量名和全局作用域的变量名可以相同,但在局部作用域中查找该变量时,会返回局部作用域的变量值,遮盖了全局的变量。

2-2. 作用

提供变量查找的机制。

访问局部变量比访问全局变量快。 这种机制的保护机制是闭包。

2-3. 性能优化

全局变量局部化,避免作用域的层层嵌套。

JS 引擎内部属性[[scope]]包含了一个函数被创建的作用域中对象的集合,即作用域链。

『加入书签,方便阅读』