对象在内存中的存储特点
前面几个章节重点去讲解了我们对象的封装(对象的创建)以及对象的继承。经过这些学习以后,我们对于对象这种数据类型有以下向个认识
-
对象是一种复杂的数据类型
在我们JavaScript所有的数据类型里面,有5种基本数据类型和一种复杂数据类型(复杂数据类型指的就是我们的对象)
-
基础数据类型我们使用
typeof
关键字去检测,而复杂数据类型如果使用typeof
关键字去检测得到的结果都是Object
。所以复杂数据类型如果要检测我们使用的是另一个关键字instanceof
。所以我们new一个构造函数也称之为创建一个对象的实例(也叫实例化一个对象)变化的赋值称之为初始化,对象的创建称之为实例化
基础数据类型与复杂数据类型其实是有本质的特点的,这个特点表现看是一不同的,基本本质在内存结构上面
当程序运行的时候所有的数据都是保存在计算机的内存当中,基本数据类型与复杂数据类型的保存形式是不一样的。我们现在通过一个简单的例子来看一下
var a = "标哥";
var b = a;
console.log(b);//现在的b是什么? //打印 标哥
a = "杨兰芳";
console.log(b); //现在的b又是什么 //打印 标哥
通过上面的代码,我们得到结论,两个变量之间互不干扰,a与b是相互独立的(基本数据类型的特性)
现在再看下面的代码
var obj1 = {
userName: "标哥",
age: 18
}
var obj2 = obj1;
console.log(obj2.userName); //打印什么 //打印 标哥
obj1.userName = "杨兰芳";
console.log(obj2.userName); //打印 杨兰芳
通过上面的代码,我们又得到结论,两个对象之间通过这种赋值是相互影响(复杂数据类型的特点)
要弄清楚上面的问题,我们就不得不从内存的角度去考虑问题(数据结构)
之前给大家讲过,内存中的数据结构分为四大部分,主要是“堆,栈,链,表”。堆与栈分别就是存储我们JavaScript数据的两大内存结构。我们的基本数据类型是保存的内存的中栈里面去的,而复杂数据类型(对象)是保存在内存的堆里面去的
对象是将实际数据放在的堆里面,然后再将个堆里面内存地址放在了栈里面, 这个栈里面存的数据实际上面就是引入堆里面的数据的内存地址,所以我们的复杂数据类型也叫引用类型
-
复数数据类弄赋值是地址赋值
-
基本数据类型的赋值是拷贝赋值
var arr1 = ["a", "b", "c", "d", "e"];
//数组是一个对象,所以具体值 是放在内存的堆里面,然后地址存放在栈里面 arr1存放的是内存地址
var arr2 = arr1;
//将arr1里面放在的东西放一份到arr2里面去,arr2也就是个内存地址
//arr1与arr2存的内存地址是同一个地址,那么就指向堆里面的同个内存对象
arr1[0] = "标哥";
//通过arr1里面存的地址找到了存放在堆里在的数组,然后改变这个数组的第一个元素
//现在的arr2有没有影响????
在工作当中,我们经常都会涉及到两个对象之间拷贝以后互不影响,这怎么办呢?。这个时候我们就会接触到一个新的概念叫对象的深拷贝
对象的深拷贝
var obj1 = {
userName: "标哥",
age: 18
}
var obj2 = obj1;
上面的代码是将obj1拷贝了一份给了obj2。但这种拷贝过程,我们叫浅拷贝。为什么叫浅拷贝是因为这个时候的操作过程只局限在了内存的栈里面。我们现在希望深入到内存的堆里面去拷贝,在内存的堆里面把这个对象也拷贝一份出来 ,这样两个对象之间就互不影响了
现在我们尝试着将上面的obj1
进行一次深拷贝
简单对象的深拷贝
var obj1 = {
userName: "标哥",
age: 18
}
Object.defineProperty(obj1, "sex", {
value: "男",
enumerable: false, //不可遍历的
writable: true,
configurable: false
});
//现在我们要把不可遍历的属性也拷贝出来
var obj2 = {};
var propertNamesArr = Object.getOwnPropertyNames(obj1); //拿到obj1所有的属性名
//遍历所有的属性名然后开始赋值
propertNamesArr.forEach(function (item, index, _arr) {
//item代表遍历的每一项,也就是obj1对象里面每一个属性名
obj2[item] = obj1[item]; //将obj1的属性值取出来赋值给obj2
});
在这里一定要注意,不能使用for...in
或Object.keys()
这种方式去获取属性,通过这种方式是不能够找到enumerable:false
的属性的,所以我们只能够使用Object.getOwnPropertyNames()
这一个方法
数组的深拷贝
这个地方也只是简单数组的深拷贝
var arr1 = ["a", "b", "c", "d", "e"];
//这的深拷贝原理与之前的对象的深拷贝原理很相似
/* 第一种方式
var arr2 = new Array(arr1.length);
//遍历数组
for (var i = 0; i < arr1.length; i++) {
arr2[i] = arr1[i];
}
*/
/* 第二种方式
var arr2 = arr1.map(function (item, index, _arr) {
//item代表遍历每一项,我现在只需把这项拿出来,返回出去,组成个新的数组就可以了
//而map的遍历方法就可以将每次回调函数的返回值组成一个新的数组
return item;
});
*/
//第三种方式
var arr2 = arr1.concat();
var arr3 = arr1.slice();
通过Object.assin()
实现简单对象拷贝
它的语法格式如下
var 目标对象 = Object.assign(目标对象,源对象);
它会将源对象拷贝到目标对象里面去,同时也返回了这个拷贝好了的对象
var obj1 = {
userName: "标哥",
age: 18
}
Object.defineProperty(obj1, "sex", {
value: "男",
enumerable: false, //不可遍历的
writable: true,
configurable: false
});
//在JavaScript里面,其实系统已经自带了一份深拷贝的方法
// 首先先创建一个新的对象
var obj2 = {};
var obj3 = Object.assign(obj2, obj1);
//它将obj2拷贝好了以后赋值给了obj3 obj2 == obj3 这是true
这个时候得到的obj2
就是拷贝好的对象,但是要注意obj1
对象里面的属性sex
没有被拷贝过程,因为Object.assign()
不能够拷贝enumerable:false
的属性
复杂对象的深拷贝
所谓复杂对象的深拷贝就是在对象里面又包含对象的情况之下将它们进行拷贝
var classInfo = {
className: "H2001",
classAddress: "湖北省武汉市",
students: ["杨兰芳", "赵聪", "梁心悦", "库威", "陈云"],
teacher: {
userName: "标哥",
sex: "男",
age: 18
},
rank: null
}
上面的对象我们使用下面的方式进行一次深拷贝
//我给你一个对象,你给一个拷贝好的新的对象
function deepCopy(oldObj) {
//在接收参数的时候,我们要做一次判断操作 ,如果是基本类型怎么办,如果是null或undefined咋办
if (typeof oldObj != "object" || oldObj == null) {
return oldObj;
}
if (Array.isArray(oldObj)) {
//说明它是一个数组
var newObj = [];
} else {
//说明它是一个对象
var newObj = {};
}
Object.getOwnPropertyNames(oldObj).forEach(function (item, index, _arr) {
if (typeof oldObj[item] == "object") {
//要么是对象,要么就是null
if (oldObj[item] === null) {
newObj[item] = oldObj[item];
} else {
//说明它真是一个对象,那又要去做深拷贝
var _temp = deepCopy(oldObj[item]);
//这个_temp就是你所拷贝出来的这个对象
newObj[item] = _temp;
}
} else {
//基本类型 或 function
newObj[item] = oldObj[item];
}
});
return newObj;
}
var a = copyObj(classInfo); //将对象拷贝了一份
但是当我们使用上面的方式完成了一次深拷贝以后,我们发现原来对象里面的students
是一个数组,被拷贝成了一个对象 。如下图所示
所以我们现在在思考一个问题就是怎么将这样的对象转变成一个数组
我们已经学习过怎么样将类数组转换成数组了,所以现在这个操作对于我们来说已经没有难度了。现在我们就将上面的方法做一次完整的书写
function copyObj(oldObj) {
var newObj = {};
var propertyNamesArr = Object.getOwnPropertyNames(oldObj);
propertyNamesArr.forEach(function (item, index, _arr) {
if (typeof oldObj[item] == "object") {
//null检测出来也是object,所以要判断一下是不是null
if (oldObj[item] != null) {
newObj[item] = copyObj(oldObj[item]);
//再次判断一下,它是不是数组
if(Array.isArray(oldObj[item])){
//我们还要将之前拷贝对象变成一个数组
newObj[item] = Array.prototype.slice.call(newObj[item]);
}
} else {
newObj[item] = null;
}
} else {
newObj[item] = oldObj[item];
}
});
return newObj;
}
var a = copyObj(classInfo); //将对象拷贝了一份
这才是完完整整的对象深拷贝,这个时候拷贝的对象如下图所示
评论区