当我们创建了一个对象,然后赋值给一个变量后,内存中究竟发生了什么?如果把一个保存了对象的变量赋值给另外一个变量,它们是同一个对象吗?
以上问题,就是本章要理清楚的内容。本章将深入到底层,去查看对象创建后内存中究竟发生了什么事。
了解本章的内容,可以让你对对象有一个更加清晰的认识,也会规避一些常犯的低级错误。
对象的赋值
const user = {
loginid: "bangbangji",
loginpwd: "123456",
name: "棒棒鸡"};
上面的代码创建了一个对象,然后赋值给了一个变量user
,完整的描述如下:
- 内存中开辟了一块空间,来存放对象的三个属性
- 将该内存区域的地址保存到变量user中
结果就是,变量user
中保存的内存地址,指向了对象所在的内存空间,如下图所示:
现在,我在上面的代码基础上加一句:
let user2 = user;
这句代码是什么意思呢?我们知道,把一个变量的值赋值给另一个变量,是指将变量中保存的东西赋值给另一个变量。而这里的变量user
保存的是什么呢?对了,是某个对象的内存地址,将其赋值给变量user2
,因此user2
中也保存了相同的内存地址。如下图:
那么,现在,我试着改变user2
中的属性:
user2.loginpwd = "666";console.log(user.loginpwd, user2.loginpwd); //将输出什么呢?console.log(user === user2); //将输出什么呢?
注:对象之间的严格相等比较(===)和相等比较(==)一样,都是比较两个对象的地址是否相同。
试着结合前面的原理图,看你是否能说出上面代码的运行结果。
由于user
和user2
中保存的相同的内存地址,指向的是同一个对象,因此,通过user2
修改了对象的登录密码,user
指向的对象也同时得到了修改,因为它们指向的是同一个东西:
因此,上面的代码输出:
666 666
true
那现在我继续增加代码,现在我要对user2
进行重新赋值,看看又会怎样呢?
user2 = {
loginid: "bbj",
loginpwd: "666",
name: "棒棒鸡"};console.log(user.loginid, user2.loginid); //将输出什么呢?console.log(user === user2); //将输出什么呢?
这种情况下,user
和user2
还指向的是同一个对象吗?不再是了!因为我又新创建了一个对象(回忆一下上一节的知识,使用{}
或new
都表示创建对象),并把这个新的对象地址保存到user2
中,如下图:
因此,上面的代码输出:
bangbangji bbj
false
面对多层次的对象结构,道理是一样的:
const user1 = {
loginid: "bangbangji",
loginpwd: "123456",
name: "棒棒鸡",
address: {
province: "四川省",
city: "成都市"
}
};const user2 = {
loginid: "xiaobaitu",
loginpwd: "654321",
name: "小白兔"};//将user1中属性address保存的地址赋值给user2的属性addressuser2.address = user1.address;
user2.address.city = "广元市";console.log(user1.address.city, user2.address.city); //输出:?
当代码user2.address = user1.address;
运行后,会得到如下的内存结构图:
虽然user1
和user2
保存着两个不同的地址,分别指向两个不同的对象,但它们的属性address
却保存着相同的地址,指向的是同一个对象。
因此,上面的代码运行完成后输出的结果为:广元市 广元市
如果你理解了上面的知识,请判断下面代码的执行结果:
const user1 = {
loginid: "bangbangji",
loginpwd: "123456",
name: "棒棒鸡"};let user2 = {
loginid: "bangbangji",
loginpwd: "123456",
name: "棒棒鸡"};
user2.loginid = "bbj";console.log(user1 === user2); //输出:?console.log(user1.loginid, user2.loginid); //输出:?user2 = user1;
user2.loginpwd = "654321";console.log(user1 === user2); //输出:?console.log(user1.loginpwd, user2.loginpwd); //输出:?console.log({} === {}); //输出:?
答案如下:
false
bangbangji bbj
true
654321 654321
false
值得玩味的是最后一句代码,直接创建两个对象进行比较(不要忘了上一章的知识,{}
和new
都是在创建对象),由于是两个新创建的对象,它们的地址自然不同,因此并不相等。
对象的销毁
每个对象都会在内存中占用一块空间,如果一个对象不再使用,就应该销毁它,将这块内存空间腾出来,以作他用。
我们将销毁对象、腾出空间的过程叫做垃圾回收。
好在很多高级语言不再需要我们手动的进行垃圾回收,它们本身自带了一个垃圾回收器,会自动处理,javascript也是如此。
垃圾回收器会不断的遍历内存中存在的对象,找出那些不再使用的对象,将其销毁,回收它所占用的内存空间。
可是,垃圾回收器怎么知道哪些对象不再使用了呢?
它是通过判断引用计数来完成的,若一个对象的引用计数为0,则该对象永远无法再访问到,它将在垃圾回收器下一次扫描内存时被回收掉。
所谓对象的引用计数,是指持有该对象的内存地址,被记录的数量,下面是一个例子。
let user = {
loginid: "bangbangji",
loginpwd: "123456",
name: "棒棒鸡"};let user2 = user;
上面的代码运行后,内存中存在一个对象,该对象的地址被记录了两次(存在于两个变量中),那么该对象的引用计数为2,不会被回收。
若在上面代码的基础上追加下面的代码:
user = null;
user2 = null;
现在,刚刚创建的对象的引用计数就变成了0,因为现在没有任何空间记录了它的内存地址。
[
原文转自朗沃易课堂,原文链接:JS面向对象系列教程 - 内存中的对象
更多精彩内容可以搜索“朗沃”公众号,了解更多!