felix-cao / Blog

A little progress a day makes you a big success!
31 stars 4 forks source link

JavaScript 作用域 #59

Open felix-cao opened 6 years ago

felix-cao commented 6 years ago

理解在嵌套作用域下的变量查找。

一、编译原理

Javascript 程序中的一段代码在执行之前经历三个步骤,统称为“编译”。

二、左查询和右查询规则

左查询(LHS, Left-Hand-Side)和右查询(RHS, Right-Hand-Side)的含义是赋值操作的左侧或右侧, 但并不一定意味着就是=赋值操作符的左侧或右侧。其规则如下:

为什么要区分 LHS 和 RHS

ES5中,LHSRHS 在没找到变量时的反应是不同的,如果 RHS 查询在所有嵌套的作用域中遍寻不到所需的变量,引擎就会抛出 ReferenceError 异常。值得注意的是,ReferenceError 是非常重要的异常类型。相较之下,当引擎执行 LHS 查询时,如果在顶层(全局作用域)中也无法找到目标变量,全局作用域中就会创建一个具有该名称的变量,并将其返还给引擎。

如果是严格模式或者ES6环境下,将禁止自动或隐式地创建全局变量。因此,在严格模式中 LHS 查询失败时,并不会创建并返回一个全局变量,引擎会抛出同 RHS 查询失败时类似的 ReferenceError 异常。

三、作用域

域,scope,表示范围或区域。作用域,就是起作用的范围,即有效区域。

作用域在任何一个编程语言中都是一个基本概念。将值储存到变量当中,并在之后按照一定的规则通过变量对这个值进行访问或修改。 在要对存放在变量中的值进行操作(访问、修改或删除)时,就需要知道存放该值的变量存放在了哪里并且该如何找到代表该值的变量。

为此编程语言设计了一套良好的规则,这套规则就叫作用域。

JavaScript 作用域(Scope)定义了一套变量的访问规则,这套规则帮助JavaScript 引擎如何在当前作用域及其嵌套的子作用域中根据变量标志符进行变量的查找。

作用域又分为词法作用域动态作用域两种工作模型JavaScript 采用的时词法作用域(Lexical Scope)模型。

词法作用域 Lexical Scope (Sometimes known as static scope), 定义在词法阶段的作用域,代码一旦写好,不用执行,他的作用范围就已经确定好了,JavaScript 在编译时的词法分析阶段会保持不变。

动态作用域(Dynamic Scope)并不关心变量是在何处以及如何声明的,只关心它在何处被调用。

3.1、词法作用域

JavaScript 的作用域是词法作用域(Lexical Scope)

词法作用域: Lexical Scope (Sometimes known as static scope)

词法作用域(通常也被叫做静态作用域)是代码在编写过程中就静态体现出来了作用范围,代码一旦写好,不用执行,他的作用范围就已经确定好了,这个就是所谓的词法作用域。

即书写代码时变量声明的位置决定了其作用域。与词法作用域相反的是动态作用域,英文Dynamic Scope

3.2、全局作用域和函数作用域

JavaScript 的作用域只有两种,( 在ES6中又增加了块级作用域 )

其中全局作用域指的是在一个 .js 文件中的作用域。全局作用域往往包含了公有化的特点: 变量公有化和函数公有化。也就是说全局作用域中的变量和函数在这个 .js 都是公有的、可访问的。

函数作用域的范围相对狭小一些, 具有私有化的特点: 变量私有化和函数私有化

三、作用域嵌套

JavaScript 的作用域主要有:

当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。因此,在当前作用域中无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量,或抵达最外层的作用域(也就是全局作用域)为止。这种现象称为作用域冒泡。

借用《你不知道的 JavaScript 上卷》书中的图

image

这个建筑代表嵌套作用域。第一层楼代表当前的执行作用域,也就是你所在的位置。建筑的顶层代表全局作用域。

LHSRHS 引用都会在当前楼层进行查找,如果没有找到,就会坐电梯前往上一层楼,如果还是没有找到就继续向上,以此类推。一旦抵达顶层(全局作用域),可能找到了你所需的变量,也可能没找到,但无论如何查找过程都将停止。

四、代码案例分析

4.1、Case 1

var up = 8;

function foo() {
  console.log(up);
}

function bar() {
  var up = 88;
  foo();
}

bar();

输出8还是88呢?请仔细观察函数的书写位置及调用位置。

4.2、Case 2

var up = 8;

function foo() {
  up = 88
  function bar() {
      up = 888
      console.log(up);
  }
  bar();
}

foo();

Case1 中,函数 foo 执行时,内部查找是否有变量 up, 如果没有向上, 就根据书写的位置,查找上面一层的代码(也叫父变量对象,当然是词法结构上的),查找 up = 8, 因此输出为8

Case2 中,函数 bar 执行,内部查找变量 up, 就近原则,所以输出为 888

4.3、Case3

console.log('start:', a);
var a = 1;
console.log(a);
function a() {console.log(2)};
console.log(a);
var a = 3
console.log(a);
function a() {console.log(4)}
console.log(a);
a()

Case3 解释:

Javascript’s lexical scope, hoisting and closures without mystery.