数组和对象直接赋值存在问题:同一个Array或者Object赋值给两个不同变量时,变量指向的是同一个内存地址,所以就会造成其中一个变量改变属性值,同时改变了另外一个变量的对应属性值。
而大多数实际项目中,我们想要的结果是两个变量(初始值相同)互不影响。所以就要使用到拷贝(分为深浅两种)
一、赋值
赋值是将某一数值或对象赋给某个变量的过程,分为:
1、基本数据类型:赋值,赋值之后两个变量互不影响
2、引用数据类型:赋址,两个变量具有相同的引用,指向同一个对象,相互之间有影响
二、浅拷贝
创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
简单来说可以理解为浅拷贝只解决了第一层的问题,拷贝第一层的基本类型值,以及第一层的引用类型地址。
几种浅拷贝方法:
(1)Array.concat(),Array.slice(),Array.from()
(2)Object.assign()
(3)es6扩展运算符
三、简单的深拷贝:
深拷贝是逐层对目标对象进行复制,意味着会在栈内存中重新分配空间存储指向一个新对象的新地址指针,因此不存在改变一个对象值而引发另一个对象随之改变的问题。
1.通过 JSON 对象实现深拷贝
function deepClone2(obj) {
objClone = JSON.parse(JSON.stringify(obj))
return objClone;
}
问题:
undefined、任意的函数以及 symbol值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。函数、undefined 被单独转换时,会返回 undefined
2.通过jQuery的extend方法实现深拷贝
var array = [1,2,3,4];
var newArray = $.extend(true,[],array);
3.递归调用
function deepClone1(obj) {
//判断拷贝的要进行深拷贝的是数组还是对象,是数组的话进行数组拷贝,对象的话进行对象拷贝
var objClone = Array.isArray(obj) ? [] : {};
//进行深拷贝的不能为空,并且是对象或者是
if (obj && typeof obj === "object") {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === "object") {
objClone[key] = deepClone1(obj[key]);
} else {
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
四、包含处理循环引用问题的深拷贝
循环引用问题的产生原因可能是对象之间相互引用,也可能是对象引用了其自身,而造成死循环。解决方法是将这些引用存储起来并在发现引用时返回被引用过的对象,从而结束递归的调用。
方法一:
使用ES6新增WeakMap
function deepClone( originObj, map = new WeakMap() ) {
if(!originObj || typeof originObj !== 'object') return originObj; //空或者非对象则返回本身
if( map.get(originObj) ) { //如果这个对象已经被记录则直接返回
return map.get(originObj);
}
//将其引用记录在map中,进行拷贝
let result = Array.isArray(originObj) ? [] : {}; //初始化拷贝结果
map.set(originObj, result); //记录引用关系
let keys = Object.keys(originObj); //originObj的全部key集合
for(let i =0,len=keys.length; i<len; i++) { //递归拷贝
let key = keys[i];
let temp = originObj[key];
result[key] = deepClone(temp, map);
}
return result;
}
方法二:
es5使用方法
function deepClone(source, uniqueList) {
if (!isObject(source)) return source;
if (!uniqueList) uniqueList = []; // 新增代码,初始化数组
var target = Array.isArray(source) ? [] : {};
// ============= 新增代码
// 数据已经存在,返回保存的数据
var uniqueData = find(uniqueList, source);
if (uniqueData) {
return uniqueData.target;
};
// 数据不存在,保存源数据,以及对应的引用
uniqueList.push({
source: source,
target: target
});
// =============
for(var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep3(source[key], uniqueList); // 新增代码,传入数组
} else {
target[key] = source[key];
}
}
}
return target;
}
五、考虑symbol类型
Symbol 在 ES6 下才有,我们需要一些方法来检测出 Symble 类型。
方法一:Object.getOwnPropertySymbols(…)
方法二:Reflect.ownKeys(…)
思路就是先查找有没有 Symbol 属性,如果查找到则先遍历处理 Symbol 情况,然后再处理正常情况,多出来的逻辑就是下面的新增代码。
function cloneDeep4(source, map = new WeakMap()) {
if (!isObject(source)) return source;
if (map.get(source) return map.get(source);
let target = Array.isArray(source) ? [] : {};
hash.set(source, target);
// ============= 新增代码
let symKeys = Object.getOwnPropertySymbols(source); // 查找
if (symKeys.length) { // 查找成功
symKeys.forEach(symKey => {
if (isObject(source[symKey])) {
target[symKey] = cloneDeep4(source[symKey], map);
} else {
target[symKey] = source[symKey];
}
});
}
// =============
for(let key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
if (isObject(source[key])) {
target[key] = cloneDeep4(source[key], map);
} else {
target[key] = source[key];
}
}
}
return target;
}
参考文章:
浅析JavaScript解析赋值、浅拷贝和深拷贝的区别 - saucxs - 博客园 (cnblogs.com)