![60374920087448c74ca514acf489950d.png](https://i-blog.csdnimg.cn/blog_migrate/f0bf7e3533adb04951e58b8d4e54faf6.jpeg)
代码在执行过程中都干了哪些事(增加面试分的知识)
![3296904f23ed35bd5066df54ca801ebf.png](https://i-blog.csdnimg.cn/blog_migrate/5ad22145ad503419a9559e731d29dc29.jpeg)
1. 开胃题
let a = 12;
let b = a;
b = 13;
console.log(a);
-----------------
let a = {n: 12};
let b = a;
b['n'] = 13;
console.log(a.n);
-----------------
let a = {n: 12};
let b = a;
b = {n: 13};
console.log(a.n);
❝ 当你看到上面这三个题的时候,可能会立马说出输出的结果,但是对于它的执行过程中开辟那些内存,以及其中值发生的变化真的清楚吗?下面以这三个题来作为今天的开场白,咱们一起来聊聊JS代码在执行过程中的一系列过程:
❞
2.JS代码执行中遇到的几个‘词’
「执行环境栈内存ECStack:」 浏览器加载页面的时候,想让代码执行,首先会形成一个栈内存(执行环境栈);然后开始让代码准备执行; 「堆内存Heap」 :存储在执行过程中引用数据类型的值:对象存储的是键值对;函数存储的是字符串 「全局执行上下文EC(G):」 最开始要执行的一定是全局下的代码,此时会形成一个全局代码的执行环境(全局上下文EC(G)),把EC(G)压缩到栈内存中去执行(进栈操作);每一个函数的执行也是这样的操作;
有些上下文在代码执行完成后,会从栈内存中移除去(出栈操作),但是有些情况是不能移除去的(例如:全局上下文就不能移出去);
在下一次有新的执行上下文进栈的时候,会把之前没有移出去的都放栈内存的底部,让最新要执行的在顶部执行; 「GO全局对象=>window:」 在全局下使用var、function声明的变量会在全局对象window中也存储一份,并且建立映射机制(一边的值改变另一边值也改变); 「全局变量存储值对象VO(G):」 把在全局下声明的变量存储在VO(G)中;
「私有变量存储值对象AO(XX):」 当形成私有作用域时,私有变量对象存储在AO(XX)中
3. 结合图片理解开胃题中的执行过程
3.1 基本数据类型值和引用数据类型值的区别
JS中的数据类型目前有两大类:「基本数据类型」 和 「引用数据类型」,他们有什么区别呢?
基本数据类型包括number、string、Boolean、null、undefined、symbol、bigint......;他们都是把值存储在栈内存中的,操作的是值;
引用数据类型包括对象(普通对象、数组对象、正则对象、函数对象、日期对象......) 和 函数;他们都是把值存储在堆内存中,然后把堆内存的地址存储在栈内存中,后期操作的是都是堆内存的地址;
3.2 '='
JS中的'='不像数学函数中的'='那样,它是把「右侧的结果(如果是一个表达式或者自执行函数等都是先计算它的结果)」 赋值 「给左侧的变量」
例如:let a = 1;
就是定义了一个变量a,并且把右侧的1赋值给左侧的变量a
;
例如: let b = 1+2;
定义了一个变量b,把右侧的运算结果3赋值给左侧的变量b
3.3 变量的关联
在JS中,一个变量只能关联一个值,但是一个值却可以关联多个变量
![f4788da1b2e912a86a80d8fb1c61dc4b.png](https://i-blog.csdnimg.cn/blog_migrate/f126dd3e5b30cc7864421a45d2bdeac8.jpeg)
3.4 执行过程
let a = 12;
它的执行过程是:先创建一个值是12,放在栈内存中;然后再创建一个变量a;最后把两个关联起来;
3.5 开胃题讲解
结合上面的知识来分析一下开胃题
- 首先浏览器会先开辟一个供代码执行的栈内存
- 在栈内存中开辟一个全局代码执行的环境EC(G),让代码在这个作用域中一步步的执行
let a = 12;
let b = a;
b = 13;
console.log(a);
-----------------
let a = {n: 12};
let b = a;
b['n'] = 13;//先创建值13存储在栈内存中,然后让b执行的堆内存中的n这个属性改为13
console.log(a.n);
-----------------
let a = {n: 12};
let b = a;
b = {n: 13}; //先创建一个堆内存用来存储值,然后把堆内存的地址存储在栈内存中和变量关联,因为b这个变量只能指向一个值,因此此时b的指向就改为`n:13`的这个堆内存
console.log(a.n);
![508d08a79f29b69edb7b72443649cc0b.png](https://i-blog.csdnimg.cn/blog_migrate/926186419f366d71618de179176ef870.jpeg)
4. 变量提升
「变量提升」是在当前执行上下文中(不管是全局的还是函数执行私有的),JS代码自上而下执行之前,都是先把带var
和 function
的进行变量提升;而let
、const
等没有变量提升机制;
var
:会进行提前声明,并且会把声明的变量存储在GO全局变量对象中,之后会存在映射机制; function
:会进行提前声明+定义,但是如果function出现在if/for等这些大括号内只会提前声明,并不会定义;
5. var let function const 这四者的区别
var
和function
会进行变量提升,而let
和const
没有变量提升机制;var
和function
声明的变量在定义之前可以使用,而let
和const
声明的变量在声明之前不能使用;- 在全局执行上下文中使用
var
和function
声明的变量会向window
中也映射一份,而let
和const
不会; typeof
检测数据类型的时候有一个‘暂时性死区’的问题:如果没有定义这个变量 返回结果是undefined
,而不是报错;但是在使用let
和const
声明这个变量之前使用typeof
检测会报错;- 在
if/for
这样的大括号内,如果有使用let
或者const
定义的变量,那么它就是私有的,会存放在if/for
这个块作用域中; - 使用
const
定义的某些值是可以修改的。当const
定义基本数据类型的时候不可以修改;但是const
定义的是引用数据类型,那么就可以修改它执行的这个堆内存中的内容,此时并没有修改const
这个变量的指向。
const a = 10; //这里a是不可以修改,如果修改会报错
const obj = {age:21};
obj = [10,20]; //这是不可以修改的,如果修改会改变他的指向,因此会报错
obj.age = 22; //这是可以修改的,他修改的是obj指向的堆内存中的值,并没有改变obj的指向
6. 作用域 与 作用域链
6.1 作用域链的形成
在某一个上下文中创建函数,除了开辟堆内存和赋值之外,还多做了一件事情‘给当前函数设置作用域链[[scope]] = 当前函数创建时候所在的上下文’
6.2 作用域链的查找机制
在当前上下文中,代码执行过程中遇到一个变量,首先看它是否为私有的
- 如果是私有的,接下来的所有操作,都是操作自己的,和别人没有关系
- 如果不是私有的,则按照scopeChain作用域链进行操作,在哪个上下文这个找到,当前变量就是谁的...一直找到全局上下文为止
- 如果找到EC(G)都找不到:如果是获取变量值就会报错,如果是设置值,相当于给GO加属性
7. 函数的执行过程
「创建函数」
- 在变量提升过程中,如果有带
function
的,直接定义加创建(自执行函数和函数表达式以及if/for内的除外) - 形成自己的作用域:[[scope]] = EC(XX);
「执行函数」
- 开辟一个私有的上下文
- 进栈执行
- 初始化作用域链 [[scope-chain]] = <当前上下文,上级上下文>
- 初始化this
- 初始化arguments = {0:1,1:2,length:2}
- 形参赋值,分别给每个形参赋值
- 变量提升:var 和 function
- 代码执行
- 是否销毁上下文 :如果外面有占用则不销毁,如果没有执行完就立即销毁
8. 检测成果
❝ 下面这个题就是结合了变量提升、函数执行等的一道题目,我把解题的思路使用画图的方式展示出来了,如果还是有不明白的地方,可以在下方的讨论去交流
❞
var x=5,
y=6;
function func(){
x+=y;
func=function(y){
console.log(y + (--x));
};
console.log(x, y);
}
func(4);
func(3);
console.log(x, y);
![e5ba22c8ed7034b3ab382bf14a82478f.png](https://i-blog.csdnimg.cn/blog_migrate/a4ef47e2a533c25b4b9d46b9e6cf665c.jpeg)
9. 总结
上面关于JS中的变量提升是JS语言中需要攻克的四座大山之一,是我们之后在写程序时候快速解决BUG的基础知识之一。如果在面试过程中,可以把代码每一步的执行过程都说出来,那也是一个加分项。加油