一、前言
只有理解了基本概念‘执行上下文’。我们才能更好的理解js这门语言。 知道了var存在变量提升,内层作用域可以访问外层,而外层不能访问内层,但是这是为什么呢?这就需要理解js的执行机制了,那么我们先看以下代码 ``
showName()
console.log(myname);
var myname = '老六'
function showName(){
console.log('函数showName被执行');
}
运行代码可以知道在定义一个变量前去拿到变量得到的undefined ,这是因为在v8引擎中变量的声明会在前面,变量的赋值在后面。而函数的声明则为整体提升,v8引擎就会解读成如以下代码
var myname
function showName(){
console.log('函数showName被执行');
}
showName()
console.log(myname);
myname = '老六'
从这里可以看到声明变量会提升到当前作用域的最顶端,函数的声明会整个提升到函数的顶端,在v8引擎当中就是如上的写法。这个过程叫做编译,好当我们搞懂编译原理就会使我们对于js这门语言有极大的提升。v8先编译再执行,在编译阶段被js存放在内存当中了
二、js 执行流程
1.读取代码
读取到代码存放到内存当中,这一份代码在用户的设备当中所占据的那一块内存空间我们称这一块内存空间是这份的执行上下文对象,内存当中开辟变量环境和词法环境,最后梳理成可执行的代码进行执行。
2.编译
3.执行
三、调用栈
调用栈就是v8引擎里面维护一个空间,专门用来管理函数之间的调用关系的一种结构让我们来看以下代码并且分析下调用栈的运作
var a = 2
function add(){
var b = 10
return a + b
}
add()
读到代码的第一刻去编译,编译会创建上下文对象的
js引擎在读取到js的那一刻就可以创建出来一个栈结构
如果对上述的内容有初步了解我们可以进一步复杂一下代码进行调用栈的分析和作图
由上述两个代码分析我们可以知道调用栈在v8引擎中存在下列
- 编译总是发生在执行前一刻
- 全局和函数体的编译会生成执行上下文存入调用栈
- 当一个函数执行完毕之后,它的执行上下文就会被销毁(所以我们写成千上万行代码也不会暴栈)
而当我们把函数写成这样就会导致一直没有函数被销毁就会产生暴栈
function test(){
test()
}
test()
我们还可以通过以下代码来学习下调用栈的实例描述
四、编译的过程
var a = 1
function fn(a){
var a =2
function a(){} // 函数声明不叫变量声明
var b = a
console.log(a);
}
fn(3)
开始创建全局上下文,全局变量环境 a = un, fn = func,全局预编译结束,a = 1,fn的调用。fn 函数编译里面固定的原理,第一找形参和变量声明,a - un , b = un,实参传给形参,a = 3,a =func,编译结束函数执行,a = 2,b = 2,最后输出的就是2
由此我们可以知道编译的过程,第一我们要创建上下文执行对象,第二我们要找形参和变量声明,将形参和声明的变量名作为key,值为undefined,第三我们要统一形参和实参的值(全局没有该步骤),第四步才是去找函数声明,将函数名作为 key,值为函数体以上这四步就是v8引擎的执行步骤。
function fn(a){
console.log(a); // func
var a = 123
console.log(a); // 123
function a(){} // 函数声明
console.log(a); // 123
var b = function(){} // 函数表达式
console.log(b); // func
function d(){}
var d = a
console.log(d); // 123
}
fn(1)
先找形参和变量声明,然后编译完成之后就是代码的执行啦,大家可以试试每个console.log的输出。