一、作用域
作用域
- 词法作用域:是在写代码或者定义的时候确定的,关注函数在何处声明
- 动态作用域:是在运行时确定的(this也是),关注函数从何处调用
JavaScript 采用词法作用域,其作用域由你在写代码是将变量和块作用域写在哪里决定,因此当词法分析器处理代码时会保持作用域不变。可以理解为作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
作用域链
作用域链定义了当变量在当前上下文访问不到的时候如何沿作用域链继续查询的一套规则。
分类
- 全局作用域
- 函数作用域
- 块作用域(ES6 let)
作用
- 作用域: 隔离变量, 可以在不同作用域定义同名的变量不冲突
- 作用域链: 查找变量
二、执行上下文
由js引擎自动创建的对象, 包含对应作用域中的所有变量属性
执行上下文是指 函数调用时 在执行栈中产生的变量对象,这个变量对象我们无法直接访问,但是可以访问其中的变量、this对象等。
包含三个部分
- 变量对象(VO)
- 作用域链(词法作用域)
- this指向
类型
- 全局执行上下文
- 函数执行上下文
- eval执行上下文
生命周期
执行上下文的生命周期包括三个阶段:创建阶段→执行阶段→回收阶段,我们重点介绍创建阶段。
- 全局 : 准备执行全局代码前产生, 当页面刷新/关闭页面时死亡
- 函数 : 调用函数时产生, 函数执行完时死亡
执行上下文创建和初始化的过程
全局执行上下文
- 在执行全局代码前将window确定为全局执行上下文
- 对全局数据进行预处理
var
定义的全局变量==>undefined
, 添加为window
的属性
function
声明的全局函数==>赋值(fun
), 添加为window
的方法
this
==>赋值(window
)- 开始执行全局代码
函数执行上下文
- 在调用函数, 准备执行函数体之前, 创建对应的函数执行上下文对象
- 对局部数据进行预处理
形参变量 ==> 赋值(实参) ==> 添加为执行上下文的属性
arguments
==>赋值(实参列表), 添加为执行上下文的属性
var
定义的局部变量==>undefined
, 添加为执行上下文的属性
function
声明的函数 ==>赋值(fun
), 添加为执行上下文的方法
this
==>赋值(调用函数的对象) - 开始执行函数体代码
执行上下文栈
在全局代码执行前,JS引擎就会创建一个栈来存储管理所有的执行上下文对象
代码执行过程
- 创建 全局上下文 (global EC)
- 全局执行上下文 (caller) 逐行 自上而下 执行。遇到函数时,函数执行上下文 (callee) 被push到执行栈顶层
- 函数执行上下文被激活,成为 active EC, 开始执行函数中的代码,caller 被挂起
- 函数执行完后,callee 被pop移除出执行栈,控制权交还全局上下文 (caller),继续执行
let fn, bar; // 1、进入全局上下文环境
bar = function(x) {
let b = 5;
fn(x + b); // 3、进入fn函数上下文环境
};
fn = function(y) {
let c = 5;
console.log(y + c); //4、fn出栈,bar出栈
};
bar(10); // 2、进入bar函数上下文环境
每次函数调用时,执行栈栈顶都会产生一个新的执行上下文环境,JavaScript引擎会以栈的方式来处理它们,这个栈,我们称其为函数调用栈(call stack)。栈底永远都是全局上下文,而栈顶就是当前处于活动状态的正在执行的上下文,也称为活动对象(running execution context,图中蓝色的块),区别与底下被挂起的变量对象(执行上下文)。
三、区别
- 作用域: 静态的, 函数声明时就确定了, 一旦确定就不会变化了
- 执行上下文: 动态的, 执行代码时动态创建, 当执行结束消失
- 联系: 执行上下文环境是在对应的作用域中的
JavaScript 的执行分为:解释和执行两个阶段。
解释阶段:
- 词法分析
- 语法分析
- 作用域规则确定
执行阶段:
- 创建执行上下文
- 执行函数代码
- 垃圾回收
JavaScript 解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。执行上下文最明显的就是 this 的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。
四、总结
一个作用域下
- 可能包含若干个上下文环境;(全局作用域下有若干个函数被调用)
- 有可能从来没有过上下文环境;(函数从来就没有被调用过)
- 有可能有过(现在函数被调用完毕后,上下文环境被销毁了)
- 有可能同时存在一个或多个(闭包)
- 同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值
作用域只是一个“地盘”,其中没有变量。变量是通过作用域对应的执行上下文环境中的变量对象来实现的。所以作用域是静态观念的,而执行上下文环境是动态上的,两者并不一样。
同一个作用域下,对同一个函数的不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值,所以,作用域中变量的值是在执行过程中确定的,而作用域是在函数创建时就确定的。
如果要查找一个作用域下某个变量的值,就需要找到这个作用域对应的执行上下文环境,再在其中找到变量的值。