前言
Hello~大家好。我是秋天的一阵风
最近,在刷短视频的闲暇时光里,我意外地刷到了一道关于连续赋值的编程面试题。
起初,我以为这只是一道简单的考查引用数据类型的题目,但当我看到答案时,却感到有些困惑,甚至可以说是摸不着头脑。所以借这篇文章记录一下这道有意思的题目和探究过程,并且分享给大家。
我们话不多说,直接上题目。
一、先看题目
var a = { n: 1 };
var b = a;
a.x = a = { n: 2 };
console.log(a.x); // undefined
console.log(b.x); // {n: 2}
我将打印答案也一并附上,给大家十分钟时间,大家可以先试着分析,看与答案能否对的上。
二、回顾基础
我们在分析答案之前先回顾一下javascript中的基础知识,同学们也可以趁这个时候进行查漏补缺:
1. 原始数据类型和引用数据类型
(1)在JavaScript中,数据类型可以分为两大类:原始数据类型(Primitive Types)和引用数据类型(Reference Types)。
(2) 原始数据类型包括字符串(String)
、数字(Number)
、布尔值(Boolean)
、null
、undefined
和ES6中引入的符号(Symbol)
。
(3) 引用数据类型则包括对象(Object
)、数组(Array
)、函数(Function)
等。
2. 原始数据类型的赋值过程
对于原始数据类型,赋值是通过值传递完成的。这意味着当你将一个原始类型的值赋给一个变量时,实际上是创建了这个值的一个副本。例如:
let a = 10; // a 指向值 10
let b = a; // b 也指向值 10
3. 引用数据类型的赋值过程
对于引用数据类型,赋值是通过引用传递
完成的。这意味着当你将一个引用类型的值赋给一个变量时,你实际上是在创建一个指向内存中相同位置的新的引用。例如:
let obj1 = { name: 'Kimi' }; // obj1 指向一个对象
let obj2 = obj1; // obj2 也指向同一个对象,不是一个新的对象
当使用var
(或let
、const
)声明一个变量并将其设置为一个对象时,这个变量实际上存储的是一个指向该对象内存地址的引用
。
在这个例子中,obj1
和 obj2
都指向内存中的同一个对象。因此,如果你通过 obj2
修改了对象的属性,obj1
指向的对象也会发生相应的变化,因为它们指向的是同一个对象。
tips: 如果你对于引用对象的深拷贝方式感兴趣,可以看看我的这篇文章~:
告别老套!structuredClone:新一代深拷贝神器,让JSON.parse和JSON.stringify靠边站!
二、逐步分析
回顾完基础知识后,我们来一步步地分析打印的输出结果。
var a = { n: 1 };
- 这行代码创建了一个对象字面量,其中包含一个属性
n
,其值为1
。 - JavaScript引擎在内存的堆(heap)区域为这个对象分配空间。
- 然后,引擎在栈(stack)区域为变量
a
分配空间,并在其中存储一个指向堆中对象的引用(内存地址)。
如图所示:
var b = a;
- 这行代码创建了另一个变量
b
。 - 由于
a
是一个对象的引用,b
也被赋予了这个相同的引用。 - 这意味着
b
也指向堆中与a
相同的对象。
重点:a.x = a = { n: 2 };
好了,这个题目最关键的就是第三行进行赋值。核心就是要搞清楚执行顺序。
首先会在a指向的引用对象中(也就是{n:1}
),声明一个 x 属性,如图所示:
接着会创建一个新的引用对象,也就是{n:2}
,如图所示:
然后会将a的地址修改指向这个新的引用对象,如图所示:
最后一步,会将这个a的新地址赋值给之前的x属性中,如图所示:
这就是前三行代码执行完的最终结果,那么现在来看输出就一目了然了。
console.log(a.x);
a
此时指向的是{n:2}
,那么a.x
自然就是 undefined
了。
console.log(b.x);
b
此时指向的是 { n: 1 , x: 00bb}
, b.x
存的是 {n:2}
的地址,那么b.x
自然就是 {n:2}
。
三、在线运行网站
我还找到了一个在线运行网站: pythontutor,方便同学们更加清晰地去了解执行过程。
var a = { n: 1 };
var b = a;
a.x = a = { n: 2 };
console.log(a.x);
console.log(b.x);