如何实现拷贝
2019-08-04 本文已影响0人
你喜欢吃青椒吗_c744
实现浅拷贝
Object.assign
ES6中拷贝对象的方法,第一个参数是新对象,剩下的参数是拷贝的源对象(可以是多个)
语法:Object.assign(target, ...sources)
var target = {};
var source = {a:1};
Object.assign(target ,source);
console.log(target); //{a:1}
source.a = 2;
console.log(source); //{a:2}
console.log(target); //{a:1}
看到这里是不是有些疑问。Object.assign不是浅拷贝吗,为什么
source.a = 2;
我改变了原对象的数据,新对象的值却没改变呢?新旧对象指向的不是同一个内存地址吗?其实,在浅拷贝中,如果第一层的属性是基本类型,新值原值互不影响(就像深拷贝一样)。如果第一层的属性是复杂类型,那么拷贝的是它的栈内存的地址。在上面的例子中,对象的第一层为a:1
,是基本数据类型,所以新旧对象不会互相影响。
扩展运算符
利用扩展运算符可以在构造字面量对象时,进行克隆或者属性拷贝
语法:var newObj= { ...Oldobj };
var obj = {a:1,b:{c:1}}
var obj2 = {...obj};
obj.a=2;
console.log(obj); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}
obj.b.c = 2;
console.log(obj); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}
原理和Object.assign()一样。这里不再赘述。但是如果属性都是基本类型的值的话,使用扩展运算符更加方便。
slice
语法: newarr = oldarr.slice();
var arr1 = [1, 2], arr2 = arr1.slice();
console.log(arr1); //[1, 2]
console.log(arr2); //[1, 2]
arr2[0] = 3; //修改arr2
console.log(arr1); //[1, 2]
console.log(arr2); //[3, 2]
concat
连接数组
let arr = [{a:1},{a:1},{a:1}]
let arr2 = [{b:1},{b:1},{b:1}]
let arr3 = arr.concat(arr2)
arr2[0].b = 123
console.log(arr3) //[{"a":1},{"a":1},{"a":1},{"b":123},{"b":1},{"b":1}]
第一层属性为复杂数据类型,所以拷贝的是栈内存的地址。原对象的值改变,新对象的值也会发生改变。
实现深拷贝
简单提一下什么是深拷贝:深拷贝则是对于复杂数据类型在堆内存中开辟了一块内存地址用于存放复制的对象并且把原有的对象复制过来,这2个对象是相互独立的,也就是2个不同的地址。
一个简单的深拷贝
var obj1 = {
a: {
b: 1
},
c: 1
};
var obj2 = {};
obj2.a = {}
obj2.c = obj1.c
obj2.a.b = obj1.a.b;
console.log(obj1); //{a:{b:1},c:1};
console.log(obj2); //{a:{b:1},c:1};
obj1.a.b = 2;
console.log(obj1); //{a:{b:2},c:1};
console.log(obj2); //{a:{b:1},c:1};
JSON.stringify
JSON.stringify()是目前前端开发过程中最常用的深拷贝方式,原理是把一个对象序列化成为一个JSON字符串,将对象的内容转换成字符串的形式再保存在磁盘上,再用JSON.parse()反序列化将JSON字符串变成一个新的对象。
var obj1 = {
a:1,
b:[1,2,3]
}
var str = JSON.stringify(obj1)
console.log(str) //{"a":1,"b":[1,2,3]}
var obj2 = JSON.parse(str)
console.log(obj2); //{a:1,b:[1,2,3]}
obj1.a=2
obj1.b.push(4);
console.log(obj1); //{a:2,b:[1,2,3,4]}
console.log(obj2); //{a:1,b:[1,2,3]}
通过JSON.stringify实现深拷贝有几点要注意
- 拷贝的对象的值中如果有函数,undefined,symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失
- 无法拷贝不可枚举的属性,无法拷贝对象的原型链
- 拷贝Date引用类型会变成字符串
- 拷贝RegExp引用类型会变成空对象
- 对象中含有NaN、Infinity和-Infinity,则序列化的结果会变成null
- 无法拷贝对象的循环应用(即obj[key] = obj)
虽说通过JSON.stringify()方法深拷贝对象也有很多无法实现的功能,但是对于日常的开发需求(对象和数组),使用这种方法是最简单和快捷的
别人总结的深拷贝的方法
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
const deepClone = function (obj, hash = new WeakMap()) {
if (obj.constructor === Date) return new Date(obj); //日期对象就返回一个新的日期对象
if (obj.constructor === RegExp) return new RegExp(obj); //正则对象就返回一个新的正则对象
//如果成环了,参数obj = obj.loop = 最初的obj 会在WeakMap中找到第一次放入的obj提前返回第一次放入WeakMap的cloneObj
if (hash.has(obj)) return hash.get(obj)
let allDesc = Object.getOwnPropertyDescriptors(obj); //遍历传入参数所有键的特性
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc); //继承原型链
hash.set(obj, cloneObj)
for (let key of Reflect.ownKeys(obj)) { //Reflect.ownKeys(obj)可以拷贝不可枚举属性和符号类型
// 如果值是引用类型(非函数)则递归调用deepClone
cloneObj[key] =
(isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ?
deepClone(obj[key], hash) : obj[key];
}
return cloneObj;
};
let obj = {
num: 0,
str: '',
boolean: true,
unf: undefined,
nul: null,
obj: {
name: '我是一个对象',
id: 1
},
arr: [0, 1, 2],
func: function () {
console.log('我是一个函数')
},
date: new Date(0),
reg: new RegExp('/我是一个正则/ig'),
[Symbol('1')]: 1,
};
Object.defineProperty(obj, 'innumerable', {
enumerable: false,
value: '不可枚举属性'
});
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj
let cloneObj = deepClone(obj);
console.log('obj', obj);
console.log('cloneObj', cloneObj);
for (let key of Object.keys(cloneObj)) {
if (typeof cloneObj[key] === 'object' || typeof cloneObj[key] === 'function') {
console.log(`${key}相同吗? `, cloneObj[key] === obj[key])
}
}