最近学习的时候看到一位老师讲了一道题,提到了数据在内存中的存储位置,个人感觉收获挺大了,来分享一下
题目为
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
a.x
b.x
在分析这道题之前,想先来说说内存图,就是数据在内存中的存储方式及位置
栈内存与堆内存
JavaScript的数据类型有Number、String、Boolean、null、undefined和object(暂时不考虑symbol),前五者称为基本类型,它们直接存放在栈内存中,而object称为复杂类型,它在栈内存中只存放了一个地址,地址指向堆内存中object数据的存储地址,例
var n = 1;
var b = true;
var obj = {name: 'yyzcl'};
内存图如下所示

从图中可以看到,基本类型的值存放在栈内存中,而复杂类型的数据其实是放在堆内存中,在栈内存中只存放了一个地址指向对内存中的数据
几个例子
第一题
var a = 1;
var b = a;
b = 2;
a // 1
内存图表示为

从图中可以看到,在
var a = 1;b = a;执行后,a和b都为1,而又执行了b = 2;,b的值被改变了,这个改变直接发生在栈内存中,b的改变并未影响到a,所以a的值还是1。
第二题
var a = {name: 'yyzcl'};
var b = a;
b = {name: '123'};
a.name // "yyzcl"
内存图表示为

在执行前两句定义之后,
a和b都在栈内存中拥有一个相同的地址,指向堆内存中的真实数据地址,而后给b重新赋值一个新对象时,堆内存中会生成一个新对象数据,b在栈内存储存的地址会发生改变,指向堆内存的新对象。但此时a的栈内存值未发生改变,堆内存的对象数据也未被改变
第三题
var a = {name: 'yyzcl'};
var b = a;
b.name = '123';
a.name // "123"
内存图表示为

这里和2有一点点区别,就是
b在声明之后,只给b其中的一个value重新赋值了,这时候就会改变堆内存中的数据,而a在栈内存存放的地址也指向这个数据,所以a的数据也跟着被改变了
第四题
var a = {name: 'yyzcl'};
var b = a;
b = null;
a //{name: 'yyzcl'}
内存图表示为

从代码中可以知道,
a、b在声明之后,重新给b赋值一个基本类型的数据,所以直接改变了b在栈内存中的值,将之前存放的堆内存地址更换为null,b与堆内存的数据断开联系,而a及堆内存数据并未发生改变
重新看那道题
var a = {name: 'yyzcl'};
var b = a;
a.x = a = {name: '123'};
a.x // undefined
b.x // [object Object]
其实要弄清楚这道题,光知道数据在内存的存储方式是不够的,还需要知道JavaScript运算符的优先级。在JavaScript中,等号的运算其实是从右向左的。我们一步步来看a和b的变化
首先是声明完成之后的状态

a和b的栈内存数据相同,都指向堆内存中的数据再来看这一句
a.x = a = {name: '123'};首先是从左向右,看
a.x,内存图变化为
从图中可以看到,在
a所指的堆内存数据中新增了一个key,其对应的value还未定义,此时a.x表示的栈内存地址还是ADDR 101(这很关键 !!!!!)再看
a = {name: '123'},这段没什么好说的,就是在堆内存中新生成一个对象,并用a在栈内存中的地址数据指向它,内存图表示为
接下来就是重点了
这里让大家判断一下此时
a.x在栈内存中存放的地址
A. ADDR 101
B. ADDR 200
正确结果是A,没错,此时a.x表示的地址是ADDR101,而后面那个a的地址已经变成了ADDR200,所以此时语句可以这么理解
a.x
(ADDR 101)= a(ADDR 200)= {name: '123'}
所以内存图变化为

才有
a.x // undefined
b.x // [object Object]
a指向的是{name: '123'},根本没有x属性,所以是undefined
而b指向堆内存中ADDR 101的对象,其中x的值是一个地址ADDR 200,指向{name: '123'}这个对象,所以值为[object Object]
要弄清楚这个问题关键要知道复杂类型的数据在内存中的存放方式以及JavaScript运算符的优先级
如有错误之处,还望不吝指教