Appearance
在代码执行前,会先进行词法分析、跟预编译,最后再解析执 行,词法分析 --> 预编译 --> 解析执行
词法分析
- 当我们打开一个网页时,浏览器都会去请求对应的 HTML 文件。虽然平时我们写代码时都 会分为 JS、CSS、HTML 文件,也就是字符串,但是计算机硬件是不理解这些字符串的, 所以在网络中传输的内容其实都是 0 和 1 这些
字节数据
。 - 当浏览器接收到这些字节数据以后,它会将这些字节数据转换为
字符串
- 将语法(字符串)分析成一个最小单位,也叫
token
, 将这个 token 解析翻译成 AST。 检查是否有低级错误,比如多了一个括号,少了一个分号等等。
预编译
预编译:准备各种需要运行的环境。这也是我们要弄明白作用域的根本步骤。这个阶段它做 了如下的工作:
- 创建 AO(GO)对象
- 找形参和变量声明,将形参和变量名作为 AO(GO)属性名,此时值为 undefind
- 实参和形参合并
- 在函数体里面找函数声明,值赋予函数体。
预编译可以分为:
- 全局预编译
- 创建 GO 对象 (Global Object)
- 找全局的变量声明,将变量声明作为 GO 的属性名,值为 undefined (var)
- 在全局找函数声明,将函数名作为 GO 对象的属性名,值赋予函数体 (function)
- 局部预编译
- 创建 AO 对象 (Activation Object)
- 找形参和变量声明,将变量声明和形参作为 AO 的属性名,值为 undefined
- 将实参和形参统一
- 在函数体内找函数声明,将函数名作为 AO 对象的属性名,值赋予函数体
AO/GO 是指活动对象或者说是执行期上下文,执行期!!!而非执行上下文
- 根据预编译的特点,也能够理解为什么 var 跟 function 会有变量提升,var 出来的变 量被赋值成 undefined,函数则赋予函数体
- let 跟 const 是 es6 的新特性,在执行上下文的创建阶段并不会被赋值。
作用域
- 在函数创建(定义)时,就创建了作用域
- 作用域就是代码的执行环境,活动对象/执行期上下文的集合就是作用域。预编 译创建的正是活动对象
- 作用域分为:
- 全局作用域
- 局部作用域(函数作用域)
实例看问题:
function a(){
function b(){
var bb = 234
aa = 0
}
var aa = 123
b()
console.log(aa) // 输出:0
}
var global = 100
a()
function c(){
var b = 1;
function a(){
console.log("111,"+ b );
var b = 2;
console.log("222,"+ b );
}
a();
console.log("333,"+ b );
}
c();
输出:111,undefined 222,2 333,1
分析:
- 对于全局而言,它的全局作用域就是全局活动对象
- 对于 a 函数而言,它的作用域就是 a 的活动对象 + 全局活动对象
- 对于 b 函数而言,它的作用域就是 a 的活动对象 + b 的活动对象 + 全局活动对象
所以当在 b 函数中修改在 a 函数定义的 aa 的值时,它也就是修改了他们共同的引用,所 以在 a 函数中打印会变化。所以作用域其实就是活动对象的集合。
作用域链
作用域链其实就是将活动对象串联到一起的一个集合概念,当访问一个变量时,会在该 变量所在的作用域中去寻找,逐级往上。
- 作用域链在 JS 内部中是以数组的形式存储的,数组的第一个索引对应的是函数本身的 执行上下文,也就是当前执行的代码所在环境的变量对象,下一个索引对应的空间存储 的是该对象的外部执行环境,依次类推,一直到全局执行环境
- 当在 Javascript 中使用一个变量的时候,首先 Javascript 引擎会尝试在当前作用域下 去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已 经到了全局作用域,如果在全局作用域里仍然找不到该变量,它就会直接报错。
- 每一个函数都有自己的一个作用域链,访问这个函数的变量时,遵循这个函数产生的作用 域链去寻找。变量的查找一定是遵循作用域链的向上搜索
执行上下文
JS 代码在执行前,JS 引擎总要做一番准备工作,这份工作其实就是创建对应的执行上下 文;
执行上下文有且只有三类,全局执行上下文,函数上下文,与 eval 上下文
创建执行上下文的方式
- 全局执行上下文(只有一个,由浏览器创建,能够使用 this 访问它),我们通过 var 创建的全局对象,都可以通过 window 直接访问
- 函数执行上下文(能有无数个,当函数被调用时创建,函数执行完销毁)
- eval
执行上下文的栈
- 调用栈: LIFO(Last In First Out 后进先出,也就是先进后出)
- 栈底永远有一个全局执行上下文(window),它在浏览器关闭时出栈
- 同步执行,只有栈顶的上下文在执行,其余在等待
function f1() {
f2();
console.log(1);
};
function f2() {
f3();
console.log(2);
};
function f3() {
console.log(3);
};
f1();//3 2 1
创建阶段跟执行阶段
创建阶段
ExecutionContext = {
// 确定this的值
ThisBinding = <this value>,
// 创建词法环境组件
LexicalEnvironment = {},
// 创建变量环境组件
VariableEnvironment = {},
};
- 绑定 this
- 创建词法环境: 存储函数声明与 let、const 声明的变量, 包括环境记录与外部环境引 入记录
- 创建变量环境: 仅仅存储 var 声明的变量包括环境记录与外部环境引入记录
- 在执行上下文创建阶段,函数声明与 var 声明的变量在创建阶段已经被赋予了一个值 ,var 声明被设置为了 undefined,函数被设置为了自身函数,而 let const 被设置为 未初始化。这就是为什么 var 跟函数有变量提升,而 let,const 没有的原因
执行阶段
代码执行时根据之前的环境记录对应赋值,比如早期 var 在创建阶段为 undefined,如果 有值就对应赋值,像 let、const 值为未初始化,如果有值就赋值,无值则赋值 undefined。