前言
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);