innerWang / blogs

1 stars 0 forks source link

js作用域和执行上下文 #22

Open innerWang opened 5 years ago

innerWang commented 5 years ago

1. 声明提升

在js中,即使先使用变量后进行声明也不会报错,如下:

console.log(a)
var a =1;

上述结果会输出 undefined,却不会报错,原因在于js的声明提升。需要注意的是,和变量的声明会前置一样,函数的声明同样会前置,若是使用函数表达式则规则与变量一样。

f1()    // 1
function f1(){
   console.log(1)
}
console.log(fn) //undefined
fn()   // 报错: Uncaught TypeError: fn is not a function
var fn = function(){ console.log(1)};

2. 作用域

作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。

通俗的理解,作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也即作用域最大的用处就是可以隔离变量,不同作用域下同名变量不会有冲突。我们常会使用立即执行函数来造出一个作用域。

在ES6之前JavaScript没有块级作用域,只有全局作用域和函数作用域,ES6通过使用 let 和 const 来提供块级作用域。

2.1 全局作用域和函数作用域

在代码中任何地方都可以访问到的对象拥有全局作用域,一般而言以下几种情形拥有全局作用域:

函数作用域是指声明在函数内部的变量,与全局作用域相反,局部作用域一般只在固定的代码片段内可访问到。

作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行。需要注意的是,块语句(如if和switch条件语句或for或while循环语句),不会创建一个新的作用域。

2.2 块级作用域

块级作用域用过let和const声明,所声明的变量在指定块的作用域外无法被访问。

块级作用域有以下几个特点:

for(let i=0; i<3; i++){ let i='abc'; console.log(i); }

// 连续输出三次 abc, 这表明函数内部的变量i与循环变量i有各自的作用域。



#### 3. 作用域链

首先需要了解一下“自由变量”的概念,若是在函数fn中使用了变量a,但是却没有在fn中声明a,则对于函数fn而言,变量a就是一个自由变量。

我们该如何获取自由变量的值呢?

在代码执行时,若在函数中找不到变量的声明,则会向**创建这个函数的作用域中寻找**,注意此处是“创建”而不是“调用”,即为所谓的“静态作用域”。

JavaScript 采用词法作用域,也就是**静态作用域**,即函数的作用域在函数定义的时候就决定了。
与词法作用域相对的是动态作用域,即函数的作用域是在函数调用的时候才决定的。

#### 4. 作用域和执行上下文

JavaScript的代码执行分为两个阶段: 解释和执行。解释阶段由编译器完成,会将代码翻译为可执行代码。执行阶段则由引擎完成。

**解释阶段**
* 词法分析
* 语法分析
* 作用域规则确定

**执行阶段**
* 创建执行上下文
* 执行可执行代码
* 垃圾回收

JavaScript在解释阶段便会确定作用域规则,但是执行上下文是函数执行之前创建的。执行上下文最明显的就是**this**的指向是执行时确定的。

作用域和执行上下文的最大区别是: 执行上下文在运行时确定,随时可以改变,但是作用域在定义时就确定,不会改变。

#### 5. 执行上下文

上面我们提到了在js的执行阶段会创建执行上下文和执行可执行代码,所谓的可执行代码即指: 全局代码、函数代码以及eval代码。

执行上下文( execution context,下面也称执行环境 )定义了变量或函数有权访问的其他数据,每个执行环境都有一个与之关联的**变量对象**( variable object ),变量对象保存了执行环境中定义的所有变量和函数。

全局执行环境是最外层的一个执行环境,在Web浏览器中,全局执行环境被认为是window对象,某个执行环境中的所有代码执行完毕后,该环境会被销毁,保存在其中的所有变量和函数定义也随之销毁。全局执行环境会直到应用程序退出(如关闭网页或浏览器)时才会被销毁。

当代码在一个环境中执行时,会创建变量对象的一个作用域链( scope chain )。作用域链用于保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终是当前执行的代码所在环境的变量对象,若这个环境是一个函数,则将其**活动对象**( activation object )作为变量对象。活动对象在一开始仅包含arguments这一个对象(该对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含环境,...,如此一直延续到全局执行环境,全局执行环境的变量对象始终都是作用域链中的最后一个对象。

从上面我们已经知道**上下文是在函数调用的时候才会产生**,执行上下文的生命周期可以分为以下几个阶段:
1. 创建阶段
    此阶段中,执行上下文会分别创建变量对象,建立作用域链,确定this指向。
2. 执行阶段
    此阶段中,完成变量赋值,函数引用以及执行其他代码。
3. 执行完毕后出栈,等待回收

变量对象和活动对象其实都是同一个对象,只是处于执行上下文的不同生命周期,只有处于函数调用栈栈顶的执行上下文中的变量对象,才会变为活动对象。