概念
- Execute Context:执行上下文
- Execute Context Stack:执行上下文栈(或 Call Stack 调用栈),存储代码运行期间创建的所以上下文
- Event Loop:引擎运行 js 线程的方式
- 引擎:从头到尾负责整个 JavaScript 程序的编译及执行过程
- 编译器:负责语法分析及代码生成等
- 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限
JavaScript 的运行分为两部分:编译、执行
一些问题
- 为什么使用栈结构存储运行时的执行上下文?
function second() { console.log(name); } function first() { var name = '2'; second(); } var name = '1'; first(); // 输出:1
- 进程与线程
进程:资源分配的最小单位;进程拥有独立的堆栈空间和数据段,每当启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段
线程:程序执行的最小单位;线程拥有独立的堆栈空间,但是共享数据段,它们彼此之间使用相同的地址空间,共享大部分数据,比进程更节俭,开销比较小,切换速度也比进程快,效率高
JavaScript 的执行分为两个阶段:1. 创建执行上下文;2. 执行代码;
执行上下文
执行上下文有三种类型:全局执行上下文、函数执行上下文、Eval 函数执行上下文
执行上下文的数据结构:
// ES3 例:
function foo(i) {
var a = 'hello';
var b = function bar() {
};
function c() {
}
}
foo(22);
创建阶段
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
this: { ... }
}
ES3 的函数执行上下文包括:
- scopeChain:指向上一个作用域(新概念)
- variableObject:包含函数的参数
arguments
和函数内声明的变量 - this:执行函数的调用者
在创建阶段函数内声明的变量都初始化为 undefined
,在执行阶段将在栈中存储(数据段?)或堆中存储的值赋给变量(说明有一个收集值的过程,但在创建阶段为什么就初始化了参数的值?)
函数每调用一次,都会产生一个新的执行上下文环境。因此不同的调用可能就有不同的参数。
总结:ES3 的执行上下文
创建阶段:1. 创建作用域链;2. 创建变量对象VO(包括参数,函数,变量);3. 确定this的值
激活/执行阶段:完成变量分配,执行代码
ES5 的执行上下文
FunctionExectionContext = {
this: ,
LexicalEnvironment: {
EnvironmentRecord: {
Type: "Declarative", // 环境记录分类: 声明环境记录
Arguments: {0: 20, 1: 30, length: 2}, // 函数环境下,环境记录比全局环境下的环境记录多了argument对象
},
outer:
},
VariableEnvironment: {
EnvironmentRecord: {
Type: "Declarative", // 环境记录分类: 声明环境记录
g: undefined
},
outer:
}
}
一些问题
- 词法作用域意味着作用域是由书写代码时函数声明的位置来决定的。编译的词法分析阶段基本能够知道全部标识符在哪里以及时如何声明的,从而能够预测在执行过程中如何对它们进行查找(找到所有的声明,并用适合的左右域将它们关联起来)
// 包括变量和函数在内的所有声明都会在任何代码被执行之前首先被处理(变量提升)
// 函数优先
foo()
var foo;
function foo() {
console.log('1');
}
foo = function() {
console.log('2')
}
// 这段代码会被*引擎*理解为如下形式
function foo() { // 变量提升 函数优先
console.log('1')
}
// var foo = undefined 重复的变量声明 被忽略
foo(); // 1
foo = function() {
console.log('2');
}
在传统编译语言的流程中,程序的一段源代码执行之前会经历三个步骤,统称为”编译“
- 词法分析:将代码的字符串分解成词法单元(token)
- 语法分析:将词法单元流(数组)转换成抽象语法树 AST
- 代码生成:将 AST 转换成可执行代码
任何 JavaScript 代码片段在执行前都要进行编译(通常就在执行前)
-
var
重复声明变量会被忽略、不允许使用let
const
重复声明变量,function
重复声明的变量后面会覆盖前面
作用域:全局作用域、函数作用域、块作用域({}
创建,不是 object)
let
、const
将变量绑定块级作用域
let
、const
的暂时死区:
console.log(a); // undefined (变量提升)
console.log(b); // ReferenceError: Cannot access 'b' before initialization (暂时死区)
var a = 1;
const b = 2;
块作用域里的函数声明
foo(); // b
var a = true;
if(a) {
function foo() {console.log('a')}
} else {
function foo() {console.log('b')}
}
作用域和执行上下文的区别
- 作用域:函数声明时确定
- 执行上下文:函数每调用一次,都会产生一个新的执行上下文环境,处于活动状态的执行上下文环境只有一个。(在你不知道的JavaScript这本书中,执行上下文被叫做动态作用域)
变量、函数表达式——变量声明(默认赋值为undefined)
this——赋值;
函数声明——赋值;
this
this 属于执行上下文的一个属性;由于每次调用函数都会生成一个新的执行上下文,所以每次调用函数都会给 this 赋值;this 表示函数被谁调用
箭头函数
用当前的词法作用域覆盖了 this 本来的值;this 的值同当前词法作用域对应的执行上下文的 this 的值;