欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

javascript面试题,关于连续赋值let a = {n: 1}; let b = a; a.x = a = {n: 2}; 的坑?

程序员文章站 2024-03-17 21:56:52
...

目录

一、问题引入

二、JavaScript的内存机制

三、面试题详解

四、总结


 

 

一、问题引入

首先来看一段代码

let a = {n: 1};
let b = a;
a.x = a = {n: 2};
console.log(a.x);
console.log(b);
//两个console分别打印出什么值?

许多面试题中我们都见到过他。让我们打开控制台输入代码看看:

javascript面试题,关于连续赋值let a = {n: 1}; let b = a; a.x = a = {n: 2}; 的坑?

结果分别是undefined和{n:1,x:{n:2}};结果是不是很意外。别急让我们慢慢来讲。

二、JavaScript的内存机制

要想搞懂这个问题首页我们得对js的内存机制有了解,我们先简单的说下。JavaScript的内存空间主要分为堆和栈两个部分,那么他们到底是怎么存放的呢。是堆还是栈?答案是都有

  • 基本类型:保存在栈内存中,因为这些类型在内存中分别占有固定大小的空间,通过按值来访问。基本类型一共有6种:Undefined、Null、Boolean、Number 、String和Symbol
  • 引用类型 :保存在堆内存中,因为这种值的大小不固定,因此不能把它们保存到栈内存中,但内存地址大小的固定的,因此保存在堆内存中,在栈内存中存放的只是该对象的访问地址。当查询引用类型的变量时, 先从栈中读取内存地址, 然后再通过地址找到堆中的值。对于这种,我们把它叫做按引用访问。

JavaScrip对象属于一个引用类型,也就是说他的值是保存在推内存中,而栈内存中保存的只是一个他堆内存的地址引用而已,用上面举例来说如:let a = {n:1} 的存放如图 :

javascript面试题,关于连续赋值let a = {n: 1}; let b = a; a.x = a = {n: 2}; 的坑?

变量a是{n:1}的一个地址引用,因此存放在栈里面,而{n:1}实际的值存放堆内存中。使用的时候先在栈内存中找到a在通过a找到堆内存中对应的值。有些人可能会问了,为什么要多次一举,直接存放在堆或者栈内存中不可以吗。答案是否定的。前面已经说过了,因为这种值的大小不固定,因此不能把它们保存到栈内存中。之所以在栈中保存一个地址引用通过引用的方法来找是因为栈的运算速度是要远远大于堆得运算速度的,这样做可以大大提高运算效率。正是因为这种机制,所以有了我们经常遇到也是最常见的一个深浅拷贝问题。在此我们不在过多解释。

三、面试题详解

在对JavaScript的内存机制有了简单的了解后我们在回到我们的代码中,现在让我们一行一行分析。


let a = {n: 1};
let b = a;

这两句不用多说。首先在堆内存中存放了值{n:1},在栈内存中存放了两个引用值a和b,可以用如下图 来表示:

javascript面试题,关于连续赋值let a = {n: 1}; let b = a; a.x = a = {n: 2}; 的坑?


a.x = a = {n: 2};

这是一个连续赋值的语句,也是本题的难点和重点。赋值语句顺序不用多说从右到左执行。但在代码中有一个a.x,由于 . 的优先级比 = 高会优先执行。因此代码我们可分成两部分执行。a.x(暂且看成整体A)和a.x的右边(暂且看成整体B)。

  • A:由于优先级高,所以先执行。此时a.x=操作了对象本身,于是去堆内存中寻找是否有x这个属性,没有的话会新增这个属性,并且赋值为undefined同时等待=的赋值;这也就是此时堆里面的值变为了{n:1,x:undefined},a和b的指向不变。如下图:

javascript面试题,关于连续赋值let a = {n: 1}; let b = a; a.x = a = {n: 2}; 的坑?

  • B:从右向左执行,最右边{n:2},会在堆内存中开辟一个新空间来存放{n:2},然后a={n:2}赋值,栈内存a指向{n:2},这个地方有一个疑惑(重点),之前a不是指向了{n:1,x:undefined}吗,赋值不会影响他吗?原因是因为在执行A部分(a.x=)的时候已经被挂起来等待赋值了,即使a发生了指向的变化,但也不再影响此刻的A部分(a.x)了,因为已经对A部分(a.x)进行了指向的确定,只不过他现在正在等待被赋值。a被重新赋值,此时a的指向变化了,此时内存中如下: 
//a:指向 
{n: 2}
//b指向 
{n:1,x=undefined}

 图变为如下:

javascript面试题,关于连续赋值let a = {n: 1}; let b = a; a.x = a = {n: 2}; 的坑?

再看A(a.x)= a,而a指向了{n:2},便成了A(a.x) = {n: 2}的局面;A(a.x)由于一直等待赋值一直被挂起,也就是一直保持着{n:1,x:undefined}对象x属性的访问,对象{n:1,x:undefined}由于一直有一个b的指向,所以不会被JS的垃圾回收机制给回收,赋值后对象变为了{n:1,x:{n:2}},如下图:

javascript面试题,关于连续赋值let a = {n: 1}; let b = a; a.x = a = {n: 2}; 的坑?

 

四、总结

在回到本题目,很显然,console.log(a.x),a指向的是{n:2},没有x这个属性,因此undefined,console.log(b),整个过程没有对b进行过操作,b指向一直不变,因此打印{n:1,{n:2}}。好了,大概就是这样了。总结一点类似这样的题目可以向我们本例中一样,吧堆和栈一点点画出来,然后一步步执行,注意优先级。基本不会有什么问题。好了,今天就先到这里,小白初识JS,若有什么不对的地方请各位大佬支出,大家一起进步