Appearance
JS 作用域
JavaScript 动态(解释执行)语言,不是提前编译的,编译结果也不能在分布式系统上移植
编译原理
- 分词/词法分析
- 词法最小单元(token) var, a, =, 1
- 解析/语法分析
- 解析成抽象语法树
- 代码生成
- 将 AST 转换位可执行代码
负责收据并维护所有声明的标识符(变量)的一系列查询,并有一定的规则来赋予这些标识符的访问权限
理解作用域
程序中定义变量的区域,该位置决定了变量的生命周期 变量和函数的可访问范围,作用域控制着变量的可见性与生命周期
LHS RHS
赋值操作的左侧或右侧
- LHS(赋值操作的目标是谁)
- RHS(谁是赋值操作的源头)
异常
- 查询到作用域,引用值 null 或 undefined,TypeError
- 未查询到作用域,ReferenceError
总结
- 作用域是一套规则,用于确定在何处以及如何查找变量(标识符)
- 查找的目的是为了给变量赋值,就会使用 LHS 查询
- 获取变量的值,使用 RHS 查询
- 不成功的 RHS 引用会导致 ReferenceError 异常
作用域链
通过作用域来查找变量的链条称为作用域链
- 作用域链保证对执行环境有权访问(变量函数的有序访问)
- 指向变量对象 => 变量对象包含执行环境所有变量和函数的对象(当前执行上下文的变量对象)
词法作用域
代码编译阶段就决定好,和函数如何调用的没关系 词法阶段的作用域(将变量和块作用域写在哪里决定的) 作用域是由代码中函数声明的位置来决定的,词法作用域是静态的作用域 通过词法作用域可以预测代码在执行过程中如何查找变量
闭包
词法作用域规定,内部函数可以访问外部函数声明中的变量,当通过调用一个外部函数返回内部函数后,即使该外部函数执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,把这些变量的集合成为闭包
查找作用域
- 全局环境作用域
- 所执行的环境作用域
作用域查找会在找到第一个匹配的标识符停止(遮蔽效应) 作用域查找始终从运行时所处的最内部作用域开始,逐级向外或者向上进行,直到遇见第一个匹配的标识符为止
欺骗作用域
eval with 会干扰作用域,降低性能
eval
接收一个字符串作为参数,可以执行字符串代码(动态生成的函数代码)
- 严格模式中,eval 拥有自己的作用域,无法改变所在的作用域
js
function foo(str) {
'use strict'; // 无法改变所在的作用域 输入 ReferenceError: a is not defined
eval(str);
console.log(a); // 改变了全局作用域 输出 1
}
foo('var a = 1');function foo(str) {
'use strict'; // 无法改变所在的作用域 输入 ReferenceError: a is not defined
eval(str);
console.log(a); // 改变了全局作用域 输出 1
}
foo('var a = 1');with
重复引用一个对象的多个属性的快捷方式
- 作用域泄露
js
function foo(obj) {
with(obj) {
a = 2;
}
}
var o1 = {
a: 1,
};
foo(o1); // o1.a = 2
var o2 = {
b: 1;
};
foo(o2); // o2.a = undefind
console.log(a) // 2 由于 o2 对象中没有 a 属性,导致 with 作用域中 a = 2 改变了全局作用域function foo(obj) {
with(obj) {
a = 2;
}
}
var o1 = {
a: 1,
};
foo(o1); // o1.a = 2
var o2 = {
b: 1;
};
foo(o2); // o2.a = undefind
console.log(a) // 2 由于 o2 对象中没有 a 属性,导致 with 作用域中 a = 2 改变了全局作用域性能
JavaScript 在编译阶段会进行性能优化(代码的词法进行静态分析、预先确定所有变量和函数的位置),而 eval 和 with 会导致标识符的位置是无效的,从而导致性能降低
注意
在严格模式中,eval(警告 unsafe-eval),with 无法使用
全局作用域
函数作用域
块级作用域
块级作用域是通过词法环境的栈结构实现的,而变量提升是通过变量环境来实现,两者结合就实现了JavaScript的变量提升和块级作用域