共计 3664 个字符,预计需要花费 10 分钟才能阅读完成。
我们通常将 JavaScript 归类为动态或解释执行语言,但实际上它也是一门编译语言,它有自己的编译器形式,运行在 JavaScript 引擎
中。
每个 Web 浏览器都有自己的 JavaScript 引擎形式:Chrome 有 V8
,Mozilla 有 SpiderMonkey
等。这些 JavaScript 引擎的共同点都是将 JavaScript 代码转换为编译器可以理解的语言,然后执行它。
执行上下文 Execution Context
当 JavaScript 代码运行的时候,运行 JavaScript 代码的环境形成了执行上下文,执行上下文决定代码可以访问哪些变量、函数、对象等。
我们将执行上下文简单视为运行当前代码的 environment / scope
,我们知道作用域分为 global scope
和 local scope
。
类似的,执行上下文也分为不同的类型:
全局执行上下文 – 代码首次执行时候的默认环境,在代码的整个执行过程中,只用一个全局执行上下文。
函数执行上下文 – 每当执行流程进入到一个函数体内部的时候,就会创建一个函数执行上下文,可以有任意数量的函数执行上下文。
执行栈 / 调用栈
JavaScript 是单线程的,浏览器只分配给 JavaScript 一个主线程,一次只能执行一个任务(函数),因此它在执行栈中对其他操作(事件和函数执行)形成一个任务队列,排队等候执行。
每当在浏览器中加载脚本时,栈 stack
中的第一个元素就是 全局执行上下文
。当有函数执行时,将创建一个 函数执行上下文
,并将其置于 全局执行上下文
之上。一旦函数执行完成,它就会从执行堆栈中弹出,并将控制权交给它下面的上下文中。结合上面说到的,我们看一个例子:
var name = "global variable"; | |
console.log(name) | |
function func1() {console.log("func1 被调用了。") | |
func2();} | |
function func2() {console.log("func2 被调用了。"); | |
} | |
func1(); |
当上述代码在浏览器中加载时:
- Javascript 引擎创建一个全局执行上下文
global execution context
并将其推送到当前执行栈。 - 接着进行
func1()
被调用,然后 Javascript 引擎为该函数创建一个新的函数执行上下文function execution context
并将其推送到全局执行上下文的顶部。 - 在执行
func1()
过程中,发现func2()
被调用,Javascript 引擎为该函数创建一个新的执行上下文,并将其推送到func1()
执行上下文的顶部。 - 当
func2()
函数完成时,其执行上下文从当前堆栈弹出,将控制权交给其下面的执行上下文,即func1()
函数执行上下文。 -
func1()
完成后,其执行堆栈将从堆栈中删除,将控制权交给全局执行上下文。一旦执行了所有代码,JavaScript 引擎就会从当前堆栈中删除全局执行上下文。
执行上下文阶段
执行上下文主要有两个阶段:创建阶段
和执行阶段
,接下来我们将逐一进行介绍。
创建阶段
在函数执行发生之前,JavaScript 引擎会做如下事情:
- 首先,为每个函数或变量创建与外部环境的连接,这些函数形成作用域链
scope chain
。作用链告诉执行上下文它应该包含什么,以及它应该在哪里查找解析函数的引用和变量的值。(对于全局环境,外部环境为 null。在全局作用域内的所有环境都将把全局环境作为其外部环境)。 - 扫描作用链后,将创建一个
环境存储器
,其中全局上下文
的创建和引用(Web 浏览器中的窗口),变量、函数和函数参数的创建和引用在内存
中完成。 - 最后,在第一步中创建的每个执行上下文中确定
this
关键字的值(对于全局执行上下文,this 指向 window)。
我们可以将 创建阶段
使用伪代码这样表示:
creationPhase = { // 创建阶段 | |
'outerEnvironmentConnection': { // 创建外部连接 | |
// 形成作用域链 | |
}, | |
'variableObjectMapping': {// 变量、函数和函数参数的创建和引用在内存中完成。}, | |
'valueOfThis': {}, // 确定 this 的值} |
执行阶段
这是代码在 创建阶段
形成的执行上下文中的运行的阶段,并且逐行分配变量值。
当执行开始时,JavaScript 引擎在其创建阶段对象中查找执行函数的引用。如果在当前对象中没有找到,它将沿着 作用域链
继续向上查找,直到它到达全局环境。
如果在全局环境中找不到函数引用,则将返回错误。如果找到了引用并且函数正确执行,那么这个特定 函数的执行上下文
将从栈中弹出,接着 JavaScript 引擎将移动到下一个函数,它们的 函数执行上下文
将被加入到栈中并执行,以此类推。
让我们通过示例来看看上面的两个阶段,以便更好地理解它。
let name = "webinfoq"; | |
var title = "execution context"; | |
const message = "hello world"; | |
function func1(num) { | |
var author = "deepak"; | |
let value = 3; | |
let func2 = function multiply() {return num * value;} | |
const fixed = "Divine"; | |
function addFive() {return num + 5;} | |
} | |
func1(10); |
因此,全局执行上下文
将如下表示:
globalExecutionObj = { // 全局执行 s 上下文 | |
outerEnvironmentConnection: null, // 全局上下文外部环境为 null | |
variableObjectMapping: { | |
name: uninitialized, // 在创建阶段,let 声明的变量是未初始化状态 | |
title: undefined, // var 声明的变量表示为未定义 | |
date: uninitialized, // 在创建阶段,const 声明的变量是未初始化状态 | |
func1: <func1 reference>, func1 地址引用 | |
}, | |
this: window //Global Object | |
} |
注意:
let
和const
定义的变量在创建阶段没有任何与之关联的值,但var
定义的变量在创建阶段为undefined
,
这就是为什么可以在va
r 声明之前访问变量,(得到的是undefined
),在let
和const
声明之前访问会报错(暂时性死区)。
这就是所谓的 变量提升
,所有使用 var
声明的变量都会被提升到作用域的顶部。
在 执行阶段
,完成对变量的赋值等操作。因此,在 执行阶段
,全局执行上下文global execution
看起来像这样:
globalExectutionObj = { // 全局执行上下文 | |
outerEnvironmentConnection: null, | |
variableObjectMapping: { | |
name: "webinfoq", | |
title: "execution context", | |
message: "hello world", | |
func1: pointer to function func1, // 指向 func1 的指针 | |
}, | |
this: window //Global Object | |
} |
当执行到 func1()
时,将形成新的函数执行上下文 function execution global
,创建阶段如下所示:
func1ExecutionObj = { // func1 函数执行上下文 | |
outerEnvironmentConnection: Global, // 外部环境为全局环境 | |
variableObjectMapping: { | |
arguments: { | |
0: 10, | |
length: 1 | |
}, | |
num: 10, | |
author: undefined, // var 声明的 | |
value: uninitialized, // let 声明的 | |
func2: uninitialized, // let 声明的 | |
fixed: uninitialized, // const 声明 | |
addFive: pointer to function addFive() // 指向函数 addFive 的指针}, | |
this: Global Object or undefined | |
} |
执行阶段:
func1ExecutionObj = { | |
outerEnvironmentConnection: Global, | |
variableObjectMapping: { | |
arguments: { // 先处理 arguments 参数 | |
0: 10, | |
length: 1 | |
}, | |
num: 10, | |
author: "deepak", // 变量 f 赋值 | |
val: 3, | |
func2: pointer to function func2() | |
fixed: "Divine" | |
addFive: pointer to function addFive()}, | |
this: Global Object or undefined | |
} |
Javascript 引擎创建 执行上下文
, 调用栈
。当有函数执行时,引擎就会创建一个新的 函数执行上下文
。最后所用函数执行完成后,将更新全局环境,然后全局代码完成,程序结束。
了解更多请关注微信公众号:webinfoq
。