前言
上回我们深入了解了作用域、作用域链,那么今天我们再次深入探讨一下浏览器中js的执行与机制的底层原理,聊聊调用栈
、作用域链
这一全过程,全局执行上下文
、函数执行上下文
、eval执行上下文
,这个创建、删除过程又是通过一个什么数据结构完成的呢?
面试题引入
在此之前先给各位安利一款百度最近刚刚开源的插件,vs中或者其他idea类全家桶都能够安装,comate.baidu.com/index.html?... ,你也可以之前通过vs插件搜索baiducomate,真的很智能,你们可以去试试。
ini
function bar(params) {
console.log(myName);
}
function foo(params) {
var myName = 'Tom'
bar();
}
var myName = 'Jerry';
foo();
题目很简单,但是需要你真正理解底层的执行过程
变量提升(发生在编译阶段)
在 JavaScript 中,变量提升是指在代码执行之前,变量和函数声明会被提升到当前作用域的顶部。 这意味着即使在声明之前使用变量,也不会导致错误,但此时变量的值是 undefined
。 需要注意的是,只有声明会被提升,赋值操作不会被提升。
ini
console.log(x); // 输出 undefined
var x = 5;
那么这个代码事实上长这样
ini
var x
console.log(x);
x = 5;
包含函数的变量提升
javascript
console.log(myFunction); // 输出函数定义
function myFunction() {
console.log("函数被调用");
}
在js引擎的眼中的执行上下文创建过程
之前我们谈过GO(全局作用域对象)、AO对象,那么其实每一个执行上下文中都有一个变量环境和一个词法环境,变量环境中放var的声明变量和函数声明,词法环境中放let和const声明的变量。接下来回到这个面试题引入部分
ini
function bar(params) {
console.log(myName);
}
function foo(params) {
var myName = 'Tom'
bar();
}
var myName = 'Jerry';
foo();
分析过程
这些创建的GO\AO对象都存放在哪里呢?
- 栈结构: 特殊的数组,先进后出
- 调用栈: js引擎用来追踪函数调用关系的
- 栈溢出: 调用栈超出内存限制 代码从上往下执行,在全局中首先创建GO对象,也就是全局执行上下文,存放在栈中,放入栈底,全局上下文中有变量环境与词法环境,代码从上往下执行就会发现声明了bar函数和foo函数以及myName变量。图中
(全局可执行对象打错了,应该是全局执行上下文!)
紧接着就执行代码,调用foo函数,既然要调用foo函数,首先就需要对foo函数进行预编译,创建AO对象,放入栈中,变量声明首先值为undefined,然后赋值为Tom
紧接着执行调用bar函数,此时需要对bar函数进行预编译,创建AO对象
打印myName,那么在这里就发现了bar执行上下文中没有myName,那么接下来这个属性去哪里找呢?我们都知道,执行上下文中会从词法环境开始找,然后再找变量环境,如果都没有呢?是顺着栈顶向下找吗?答案是否定的。事实上每一个执行上下文中,都存有一个outer属性,这个outer属性的规则是:我的词法作用域(在函数定义时所在的作用域)在哪里,我就指向哪里。那么在bar中,bar的词法作用域存在于全局执行上下文中,那么这个outer就指向全局执行上下文,在自己的bar执行上下文对象中没有找到myName属性,就去全局中找,全局中myName属性值为Jerry,前面的图忘记加上了,在调用foo之前,已经执行过对Myname属性赋值的语句了,这也是一个执行代码的过程
接下来bar函数执行完毕,需要对bar的执行上下文进行释放,弹栈,然后foo执行完毕,进行弹栈,在这我们能够理解,如果不弹栈,见到一个我们就需要创建一个执行上下文对象,长此以往,就会造成栈溢出
小结
本次内容虽然很简单,但是极易出错,知识点都是代码的底层逻辑,理解透彻以后才能在面试时对答如流从容面对。再次多提一下,在执行上下文中的查找过程,是从词法环境查找然后再往变量环境中查找,每一个环境又是一个小的栈结构,也是从栈顶开始向栈底查找,如图所示: