JavaScript执行原理

#积累/浏览器原理

一段 JavaScript 代码在执行之前需要被 JavaScript 引擎编译,编译完成之后,才会进入执行阶段。

编译阶段

1
2
3
4
5
6
showName()
console.log(myname)
var myname = '极客时间'
function showName() {
console.log('函数showName被执行');
}

上面一段JavaScript代码编译之后,会生成两部分内容:执行上下文和可执行代码

执行上下文

执行上下文是JavaScript执行一段代码时的运行环境,比如调用一个函数,就会进入这个函数的执行上下文,确定该函数在执行期间用到的诸如 this、变量、对象以及函数等。
执行上下文包括三种:全局执行上下文、函数执行上下文、eval执行上下文。


执行上下文中包括变量环境、词法环境、outer和this变量。
变量环境:保存函数声明和var声明的变量;
词法环境:保存let、const声明的块级作用域变量;
outer:一个外部引用,用来指向外部的执行上下文,构成作用域链;
this:this是和执行上下文绑定的,每个执行上下文中都有一个 this。

作用域链


变量的查找:先在函数执行上下文词法环境中查找,然后到变量环境;接着会到外层执行上下文继续查找,直至全局执行上下文。

原型链

作用域链作用于变量查找,而对象属性的查找则依赖于原型链。
原型链的构建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var o = {a: 1};
//原型链: o ---> Object.prototype ---> null

var a = ["yo", "whadup", "?"];
//原型链: a ---> Array.prototype ---> Object.prototype ---> null

function f(){ return 2; }
//原型链: f ---> Function.prototype ---> Object.prototype ---> null

function Graph() {
this.vertices = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function(v){
this.vertices.push(v);
}
};
var g = new Graph();
//原型链: g ---> Graph.prototype ---> Function.prototype ---> Object.prototype ---> null

var a = {a: 1};
var b = Object.create(a);
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
// 新对象的原型就是调用 create 方法时传入的第一个参数

调用栈

JavaScript属于解释型语言,每次运行时都需要通过解释器对程序进行动态解释和执行。
例如一段下面的代码:

1
2
3
4
5
6
7
8
9
10
var a = 2
function add(b,c){
return b+c
}
function addAll(b,c){
var d = 10
result = add(b,c)
return a+result+d
}
addAll(3,6)

执行过程如下:

  1. 编译全局代码,生成全局执行上下文,开始执行全局代码;
  2. 调用addAll函数,编译该函数生成addAll函数执行上下文,开始执行函数内部代码;
  3. 调用add函数,编译该函数并生成add函数执行上下文,开始执行add函数内部代码;
  4. add函数返回执行,该函数执行上下文从调用栈中弹出;
  5. addAll函数返回执行,该函数执行上下文从调用栈中弹出;
  6. JavaScript执行结束。

执行阶段

JavaScript 引擎开始执行“可执行代码”,按照顺序一行一行地执行。当执行函数和变量时,便在变量环境对象中查找。