1 前言
读完全文,希望你能明白:
2 基本类型 & 引用类型
ECMAScript 中的数据类型可分为两种:
- 基本类型:undefined、null、Boolean、String、Number、Symbol
- 引用类型:对象类型 Object type,Object、Array、Date、Function、RegExp等
不同的复制方式:
- 基本类型:从一个变量向另外一个新变量复制基本类型的值,会创建这个值的一个副本,并将该副本复制给新变量
1 2 3 4 5 6 7 8
| let foo = 1; let bar = foo; console.log(foo === bar);
let foo = 233; console.log(foo); console.log(bar);
|
- 引用类型:从一个变量向另一个新变量复制引用类型的值,其实复制的是指针(内存地址),最终两个变量最终都指向同一个对象。
1 2 3 4 5 6 7 8 9 10 11
| let foo = { name: 'leeper', age: 20 } let bar = foo; console.log(foo === bar);
foo.age = 19; console.log(foo); console.log(bar);
|
3 浅拷贝 & 深拷贝
注:深浅拷贝这个说法是针对引用类型
- 浅拷贝:仅仅是复制了引用,彼此之间的操作会互相影响
- 深拷贝:不同的地址,相同的值,互不影响
总的来说,深浅拷贝的主要区别就是:复制的是引用还是复制的是实例。
借助 ConardLi大佬 以下两张图片,帮我们更好的理解两者的含义:
3.1 浅拷贝
3.1.1 数组
1 2 3 4 5 6 7 8
| let a = [1, 2, 3, 4]; let b = a.slice(); console.log(a === b);
a[0] = 5; console.log(a); console.log(b);
|
1 2 3 4 5 6 7
| let a = [1, 2, 3, 4]; let b = a.concat(); console.log(a === b);
a[0] = 5; console.log(a); console.log(b);
|
1 2 3 4 5
| var arr = [1,2,3,4,5] var [ ...arr2 ] = arr arr2[2] = 5 console.log(arr); console.log(arr2);
|
3.1.2 对象
1 2 3 4 5
| var a = {'name': 'xiaoyu'}; var b = Object.assign({}, a); b.name = 'xiaoyu2'; console.log(a.name); console.log(b.name);
|
1 2 3 4 5
| var a = {'name': 'xiaoyu'}; var { ...b } = a; b.name = 'xiaoyu2'; console.log(a.name); console.log(b.name);
|
- 3.1.3 函数库lodash的_.clone方法
1 2 3 4 5
| var _ = require('lodash'); var objects = [{ 'a': 1 }, { 'b': 2 }]; var shallow = _.clone(objects); console.log(shallow[0] === objects[0]);
|
以上看起来似乎是深拷贝,再接着看就知道它们究竟是深拷贝还是浅拷贝:
1 2 3 4 5 6 7
| let a = [[1, 2], 3, 4]; let b = a.slice(); console.log(a === b);
a[0][0] = 0; console.log(a); console.log(b);
|
1 2 3 4 5
| var a = {'name': { 'zh': 'yushuang', 'en': 'shuangyu' } }; var b = Object.assign({}, a); b.name.zh = 'yushuang2'; console.log(a.name); console.log(b.name);
|
综上, 这些方法都并不是真正的深拷贝,对于第一层的元素是深拷贝,而第二层是复制引用。
3.2 深拷贝
- JSON.parse()和JSON.stringify()
- JSON.stringify():把一个js对象序列化为一个JSON字符串
- JSON.parse():把JSON字符串反序列化为一个js对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let obj = { name: 'leeper', age: 20, friend: { name: 'lee', age: 19 } }; let copyObj = JSON.parse(JSON.stringify(obj)); obj.name = 'Sandman'; obj.friend.name = 'Jerry'; console.log(obj);
console.log(copyObj);
|
综上,JSON.parse() 和 JSON.stringify() 是完全的深拷贝。但是,这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则,因为这两者基于 JSON.stringify 和 JSON.parse 处理后,得到的正则就不再是正则(变为空对象),得到的函数就不再是函数(变为null)了,举个例子如下:
1 2 3 4 5 6
| let arr = [1, 3, { username: ' kobe' },function(){}]; let arr4 = JSON.parse(JSON.stringify(arr)); arr4[2].username = 'duncan'; console.log(arr, arr4)
|
1 2 3 4 5 6 7 8
| var _ = require('lodash'); var obj1 = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] }; var obj2 = _.cloneDeep(obj1); console.log(obj1.b.f === obj2.b.f);
|
1
| $.extend(deepCopy, target, object1, [objectN]);
|
1 2 3 4 5 6 7 8
| var $ = require('jquery'); var obj1 = { a: 1, b: { f: { g: 1 } }, c: [1, 2, 3] }; var obj2 = $.extend(true, {}, obj1); console.log(obj1.b.f === obj2.b.f);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| function clone(target, map = new WeakMap()) { if (typeof target === 'object') { let cloneTarget = Array.isArray(target) ? [] : {}; if (map.get(target)) { return target; } map.set(target, cloneTarget); for (const key in target) { cloneTarget[key] = clone(target[key], map); } return cloneTarget; } else { return target; } }
const target = { field1: 1, field2: undefined, field3: { child: 'child' }, field4: [2, 4, 8], f: { f: { f: { f: { f: { f: { f: { f: { f: { f: { f: { f: {} } } } } } } } } } } }, };
target.target = target; const result = clone1(target); console.log(result); console.log(target);
|