本文深入解析JavaScript中引用类型赋值的执行细节。通过a.x = a = {n:2}这一经典案例,阐明赋值运算从右向左与点运算符优先级的交互机制。重点解释为何a.x最终指向新对象而输出undefined,以及b.x为何指向旧对象并打印[object Object],帮助开发者理解内存引用与属性查找的实际过程。
JavaScript引用类型赋值与属性访问的执行顺序详解
首先观察以下代码示例:
<script>
var a = {n:1};
var b = a;
a.x = a = {n:2};
console.log(a.x);// --> undefined
console.log(b.x);// --> [object Object]
</script>
这段代码看似简单,但运行结果却容易让人困惑,甚至产生误解。常见的疑问包括:“a.x已经指向了对象a,为什么log(a.x)的结果是undefined?”以及“b.x应该和a.x指向同一个对象,为什么日志中会显示出两个不同的对象?”
读者可以先尝试自行推导其中的原因和工作机理。如果能理清逻辑,便无需继续阅读后续分析。
接下来,我们将逐步拆解这段代码的执行过程,从而深入理解JavaScript中引用类型“赋值”操作的底层机制。
代码执行的第一步是:
var a = {n:1}; var b = a;
此时,变量a指向一个包含属性n且值为1的对象(暂称为对象A)。变量b被赋值为a,因此b也指向同一个对象A。也就是说,在这一阶段,a和b共同指向对象A:

这一步逻辑较为直观,我们继续分析下一行关键代码:
a.x = a = {n:2};
虽然JavaScript的赋值运算符遵循从右向左的结合顺序,但由于点运算符(.)具有最高的优先级,这行代码首先会计算a.x。
此时发生了一个关键变化——a所指向的对象(对象A)新增了一个属性x,尽管该属性的值目前为undefined:

从内存模型可以看出,由于b与a指向同一个对象A,要访问A的属性x,既可以使用a.x,也可以使用b.x。
随后,根据从右向左的赋值顺序,先执行a = {n:2}。此时,变量a的指向发生改变,转而指向一个新创建的对象{n:2}(暂称为对象B):

接着执行a.x = a。许多人会误以为这一步是“对象B新增属性x,并指向对象B自身”。
实际上并非如此。由于JavaScript引擎在第一步已经计算了a.x,此时解析器已确定a.x指的是对象A的属性x。因此,在同一条语句中后续进行赋值时,不会重新解析a.x为对象B的属性,而是继续向对象A的x属性赋值。
因此,a.x = a 的实际含义是:对象A的属性x指向了对象B:

此时结果已清晰可见。当执行console.log(a.x)时,变量a指向对象B,但对象B并没有属性x。JavaScript在查找属性时会上溯原型链,直到Object.prototype仍未找到指定属性,因此输出undefined。
当执行console.log(b.x)时,由于b仍指向对象A,b.x即访问对象A的属性x。该属性在之前的步骤中已被赋值为对象B,因此输出[object Object]。请注意,这里的[object Object]并非表示两个对象,而是对象隐式调用toString()方法后的字符串形式,即"[object Object]",它仅代表一个对象实例。
以上即为本文关于JavaScript引用类型赋值与属性访问机制的全部内容,希望读者能从中获益。