作用域

文章目录
  1. 作用域是一套规则,用于确定在何处以及如何查找变量 ( 标识符 )。如果查找的目的是为变量进行赋值,那么就会使用 LHS,如果查找的目的是获取变量的值,就会使用 RHS 查询
  2. 无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。

欺骗词法作用域

  1. eval(), 可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在程序中这个位置的代码。通常被用来执行动态创建的代码。

    1
    2
    3
    4
    5
    6
    function foo(str, a) {
    eval( str ); // 欺骗!
    console.log( a, b );
    } v
    ar b = 2;
    foo( "var b = 3;", 1 ); // 1, 3
  2. with ,通常被当做重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身。可以将一个没有或有多个属性的对象处理为一个完全隔离的词法作用域,因此这个对象的属性也会处理为定义在这个作用域中的词法标识符。

  3. js 引擎会在编译阶段进行属相的性能优化。其中有些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行阶段快速找到标志符,如果引擎发现可 eval 和with,它只能简单的假设关于标识符位子的判断都是无效的,因为无法再词法分析阶段明确知道 eval() 会接受到什么代码,这些代码会如何对作用域进行修改,也无法知道传递给 with 用来创建新词法作用域的对象的内容到底是什么.

var a = 2

编译器会进行如下的处理:

  1. 遇到 var a,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中。如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为 a。
  2. 接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理 a = 2 这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作 a 的变量。如果否,引擎就会使用这个变量;如果不是,引擎会继续查找该变量,如果引擎最终找到了 a 变量,就会将 2 赋值给它。否则引擎就会举手示意并抛出一个异常!

总结:变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。

编译器在编译过程的第二步中生成了代码,引擎执行它时,会通过查找变量 a 来判断它是否已声明过。查找的过程由作用域进行协助,但是引擎执行怎样的查找,会影响最终的查找结果。

在我们的例子中,引擎会为变量 a 进行 LHS 查询。另外一个查找类型叫做 RHS

  1. LHS: 赋值操作的目标是谁
  2. RHS:谁是赋值操作的源头

当一个块或者函数嵌套在另一个块或函数中时,就发生了作用于的嵌套。因此作用域中无法找到某个变量是,就会在外层嵌套的作用域中继续查找,知道找到该变量,或抵达最外层的作用域为 ( 也就是全局作用域 ) 止。

遍历嵌套作用域链的规则很简单:引擎从当前的执行作用域开始查找变量,如果找不到,就向上一级继续查找。当抵达最外层的全局作用域时,无论找到还是没找到,查找过程都会停止。


函数作用域和块作用域

函数作用域的含义是指,属于这个函数的全部变量都可以在整个函数的范围内使用及复用 ( 事实上在嵌套的作用用户中也可以使用 )

  1. 立即执行函数会被当做函数表达式而不是一个标准的函数声明来处理,函数声明和函数表达式之间最重要的区别是他们的名称标识符将会绑定在何处。
  2. 直接在全局中声明的函数是并绑定在全局作用域中,而立即执行函数被绑定在函数表达式自身的函数中而不是所在作用域中。
  3. 当使用 var 声明变量时,它写在哪儿都是一样的,因为它们最终都会属于外部作用域。

提升是指声明会被视为存在于其所出现的作用域的整个范围内。但是使用 let 进行的声明不会再块级作用域中进行提升。声明的代码被运行之前,声明并不 “ 存在 ”。


提升

任何声明在某个作用域内的变量,都将属于这个作用域

包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理。声明将被提升到顶部,而赋值语句将被留在原地等待执行阶段。只有声明本身会被提升,而赋值或其他运行逻辑会留在原地。如果提升改变了戴安执行的顺序,会照成非常严重的破坏。

函数声明和变量声明都会被提升。但是函数会首先被提升,然后才是变量。尽管重复的声明会被忽略掉,但出现在后面的函数声明还是可以覆盖前面的。

var a = 2; 被分为两个阶段,一个是编译阶段的任务,而第二个是执行阶段的任务。


动态作用域

1
2
3
4
5
6
7
8
9
10
词法作用域是一套关于引擎如何寻找变量以及会在何处找到变量的规则。动态作用域最重要的特征是它的定义过程发生在代码的书写阶段。
function foo() {
console.log( a ); // 2
} f
unction bar() {
var a = 3;
foo();
} v
ar a = 2;
bar();

此法作用域让 foo() 中的 a 通过 RHS 引用了全局作用域的 a,所以会输出 2;

而动态作用域并不关心函数和作用域是如何声明以及在何处声明的,值关心它们从何处调用,换句话说,作用域链是基于调用栈的,而不是代码中的作用域嵌套。因此如果 js 具有动态作用域就会输出 3。什么会这样?因为当 foo() 无法找到 a 的变量引用时,会顺着调用栈在调用 foo() 的地方查找 a,而不是在嵌套的词法作用域链中向上查找。由于 foo() 是在 bar() 中调用的,引擎会检查 bar() 的作用域,并在其中找到值为 3 的变量 a。

事实上 js 并不具备动态作用域。它只有词法作用域,但是 this 机制某种程度上很像动态作用域。

词法作用域是在写代码或者说定义时确定的,而动态作用域是在运动时确定的。(this 也是 ),词法作用域关注函数在何处声明,而动态作用域关注函数从何处调用。

分享到:
network