前言
前面我们了解了JS的预编译,本文我们来深入了解浏览器中JS的执行机制
正文
变量提升
-
js引擎在执行js的过程中把变量声明部分和函数声明部分提升到代码头部,默认赋值为undefined
-
声明提升时发生在编译阶段
-
执行上下文中放变量环境和词法环境,变量环境中放var的声明变量和函数声明,词法环境中放let和const声明的变量
当引擎执行下列代码时,会先进行编译,编译就会创建上下文对象
showName()
console.log(myName);
var myName ='Alo'
function showName(){
console.log('函数showName被执行');
}
在执行这段代码之前,JavaScript引擎会进行预编译阶段,将变量和函数声明提升到顶部。因此,代码实际上会被重构为:
var myName; // 变量声明被提升,但赋值操作没有
function showName() {
console.log('函数showName被执行');
}
showName(); // 调用函数
console.log(myName); // 打印变量myName,此时myName的值为undefined
myName = 'Alo'; // 赋值操作
我们用图解析:
调用栈
栈
在了解调用栈之前,我们先解释一下栈是个什么东西
栈的特点就是先进后出
栈在JS代码中表示为var stack = []
欸我们发现,这不是个数组吗?
确实,但是我们要把它当成栈来用
JavaScript中的数组可以通过以下方法来模拟栈的行为:
- Push:使用数组的
push()方法来在末尾添加元素,这相当于在栈顶添加元素。 - Pop:使用数组的
pop()方法来移除最后一个元素,并返回它,这也符合栈的Pop操作。
调用栈的步骤
调用栈是js引擎用来追踪函数调用关系的
我们用下面这段代码进行解释:
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执行全局的代码
var a = 2 a变为2
函数先不看,最后是addAll的调用
第三步
创建addAll执行上下文
第四步 addAll的调用
发现有add函数调用
add执行上下文中,声明环境和词法环境都没有内容
这就是完整的调用栈了
为什么使用栈
栈上的数据(如局部变量)在函数调用时自动分配和释放,这可以减少程序员管理内存的负担
当add执行完了,其执行上下文对象就会被销毁,当全部代码执行完后,全局执行上下文也会出栈
栈溢出
调用栈超出内存限制
比如下面的代码
function a() {
var num = 1
return num + b()
}
function b() {
var num = 2
return num + c()
}
function c() {
var num = 3
return num + d()
}
// .....有n多个调用
a()
要执行a 就要等b执行,要执行b,就要等c执行...这种写法就叫做回调地狱,会导致爆栈
作用域链
同样,我们先看个例子:
function bar() {
console.log(myName);
}
function foo() {
var myName = 'Alo';
bar();
}
var myName = 'Zack';
foo()
这段代码的执行结果是Alo还是Zack呢?
执行结果实际是Zack,我们来分布解析:
第一步
全局编译:
第二步
创建foo执行上下文:
第三步 创建bar执行上下文:
我们只看这个图,得出的输出结果好像就是Alo,其实不然,我们这里要引入一个outer的概念
outer
outer属性其实就是作用域链
每个执行上下文的变量环境当中都会包含一个outer,指向的是它的外层作用域(由于全局作用域就是最外层了,所以它的outer为null)
在上面那段代码中,bar里的outer其实指的是全局,而不是foo
作用域链并不是在调用栈中从上向下查找,而是当前执行上下文变量环境中的outer指向来定,而outer指向的规则是,我的词法作用域在哪里,outer就指向哪里
词法作用域:在函数定义时所在的作用域,大白话讲就是你把那个单词写在哪里面就属于哪
所以关键就是理解outer这个概念,这样我们就能明白为什么输出的是Zack而不是Alo啦
结语
以上就是本篇文章全部内容,希望能帮助读者深入理解JavaScript执行机制,包括变量提升、调用栈、作用域链以及栈溢出等核心概念。感谢您的阅读!