连续赋值问题
- 2021.04.12
关于javascript
连续赋值问题,我们先来看一段代码:
let a = {n : 1};
let b = a;
a.x = a = {n: 2};
console.log(a.x) // undefined
console.log(b.x) // {n:2}
console.log(a === b.x) // true
首先let a = {n : 1};
定义了一个引用类型变量a
,在栈中存储了一个变量a
的内存地址,在堆(内存)中开辟了一个空间存放对应的值{n:1}
。
其次let b = a;
在栈中写入了b,并将a指向堆中的地址索引赋值给了b。但是并没有开辟新的存储空间。
接下来执行a.x = a = {n:2};
这段代码的执行顺序遵循如下原则:
- 先获取等号左侧的
a.x
,但a.x
并不存在,于是JS为(堆内存中的)对象创建一个新成员x
,这个成员的初始值为undefined
。
这也是为什么直接引用一个未定义的变量会报错,但是直接引用一个对象的不存在的成员时,会返回undefined
。
创建完成后,目标指针已经指向了这个新成员x,并会先挂起,等待等号右侧的内容有结果了,再完成赋值。
接着执行
a = {n:2};
发现这是个简单的赋值语句,于是将堆中a存储的值修改为了{n:2}
。
这里需要特别注意,这个a
已经不是开头的那个a
,而是一个全新的a
。这个新a
指针已经不是指向原来的值的那个堆内存,而是分配了一个新的堆内存。但是原来旧的堆内存因为还有b
在占用,所以并未被回收。
等待上述语句执行完成后就执行之前等待的命令,将新生成
a
的索引地址赋值给了x
,因此x
的值也就变成了{n:2}
。等待上述代码执行完成后,此时
b = { n:1, x: { n:2 }}
,a = { n: 2}
。
因此也就有了上述的代码结果。
TIP
总结:
基础类型的存储是存在栈中的,引用类型的存储是在栈中存储堆的索引,在堆中开辟空间存储变量的值。
当访问一个对象的属性的时候,如果属性不存在会被赋值为
undefined
。当进行连续赋值的时候,
.
属性访问优先级会大于=
赋值,且当成员等待赋值的时候,锁定的赋值目标是成员,而非对象。对象重新赋值时,并非是修改原堆内存的值,而是重新分配堆内存,栈内存中的指针会做相应修改。
如果原堆内存有多个栈内存指向它,由于引用还存在,原堆内存的数据不会消失。如果堆内存再无其它引用,则会被JS的垃圾回收机制回收。对象的成员对象也一样。
← 数据类型转换 toString()方法 →