JS入门必读:JavaScript 执行机制

152 阅读4分钟

JavaScipt 的执行机制

今天来学习一下 js 的执行机制。来解答下 js 中为什么会存在声明提升这一问题。

1. js 执行流程

  1. 读取代码
  2. 编译
  3. 执行

重要的是:代码先编译再执行

2. 执行上下文

要了解js的执行机制,首先我们要了解什么是执行上下文,我们先来看一段代码:

showName()
console.log(myname);
var myname = '小明'
function showName() {
    console.log('函数showName执行了');
}

思考一下这一段代码的输出是什么,下面是答案:

image.png

你会发现 showName() 函数还未声明就能正常调用,为什么呢?在 v8 引擎眼里,代码是这样子的:

//===v8引擎===编译===
var myname                                //var定义的变量声明提升
function showName() {                     //函数声明整体提升
    console.log('函数showName执行了');
}

showName()
console.log(myname);

myname = '小明'

我们把它分成两个部分:

image.png

执行上下文:当 JavaScript 代码运行时,会创建一个执行上下文来管理代码的执行。简单来说,它就像是一个盒子,里面装着代码执行时所需要的所有信息。其会被规划成两个空间,变量环境和词法环境。

a685769d9810089286e15136c7de6507.png

变量环境放的是 var 的变量声明、函数声明,词法环境放的是 letconst 的变量声明, v8 引擎从中整理出可执行代码块。最后执行出结果。

执行上下文有三类:全局执行上下文、函数执行上下文、块级执行上下文

3. 调用栈

首先我们来看一段代码:

function test() {
  test()
}
test()//自己调用自己,无限循环

来看看它的运行结果:超出了预期的最大栈空间,也就是爆栈。

image.png

由此可见 js 引擎里面一定有一个栈结构,也就是本节要讲的调用栈。(栈:先进后出)

调用栈:v8引擎用来管理函数之间的调用关系的一种结构

让我们来理一理这个代码

var a = 2
function add() {
    var b =10
    return a + b
}
add() //12

来看看全局的编译

image.png

接着全局代码的执行: a 被赋值为 2 , add() 函数的调用,出现 add() 这个函数的执行上下文

由此可知:函数的编译只出现在函数被调用时

image.png

add() 开始执行,b 被赋值为 10,返回 a + b ,在 add 执行上下文找不到 a,那就会去上一个执行上下文中找,让我们看一下此时的调用栈:

699499768217c13f263fb67b2e476627.png

4. 编译的过程

  1. 创建执行上下文对象
  2. 找形参和变量声明,将形参和声明的变量名作为key值为undifined
  3. 统一形参和实参的值( 全局没有该步骤 )
  4. 在作用域里找函数声明,将函数名作为key,值为函数体

让我们再看一个例子

var a = 2
function add(b, c) {
    return b + c
}
function addAll(b, c) {
    var d = 10
    var result = add(b, c)
    return a + result + d
}
addAll(3, 6)

来看看调用栈的变化:

js 引擎读取到代码,创建一个调用栈,全局被编译,形成一个全局执行上下文。

全局执行,a = 2addAll 函数被调用,形成一个 addAll 执行上下文。

addAll 函数执行,d = 10result 的值为 add(b,c)add 函数被调用,形成一个add 执行上下文。

add 函数执行,返回 9,add 函数执行完毕,add 执行上下文被销毁。

result = 9addAll() 返回19,addALL 函数执行完毕,addAll 执行上下文被销毁。

全局执行完毕,全局执行上下文被销毁,调用栈恢复干净的状态。

f53639e8d48a5108f390d2262fed5b09.png

  1. 编译永远发生在执行的前一刻
  2. 全局和函数体的编译会生成执行上下文存入调用栈
  3. 当一个函数执行完毕后,他的执行上下文就会被销毁(出栈)

前文中这个代码输出 aundifined,我们只知道存在变量声明,现在我们就知道为什么会存在声明提升了。

console.log(a);
var a = 1;

首先,调用栈里面创建一个全局上下文,a = undefined,然后全局开始执行,执行 console.log(a),此时输出 a = undefined,然后 a 赋值为 1,执行完毕,全局上下文被销毁。

5. 练习

让我们来看一道面试题

var a = 1;
function fu(a) {
    var a = 2;
    function a() {}
    var b = a
    console.log(a);
}
fu(3);

它编译结束的调用栈:

image.png

执行之后,输出为 2