Skip to content

JS 作用域

JavaScript 动态(解释执行)语言,不是提前编译的,编译结果也不能在分布式系统上移植

编译原理

  1. 分词/词法分析
  • 词法最小单元(token) var, a, =, 1
  1. 解析/语法分析
  • 解析成抽象语法树
  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的变量提升和块级作用域

Released under the MIT License.