现象
js真浅拷贝指的是对引用数据类型而言。
基本类型:Number,String,Boolean,Null,Undefined ,Symbol
引用类型:Object,Array,Date,RegExp,Function
引用类型地址存放在栈中,对象实际存放在堆中。具体可以查看文章js函数的参数都是按值传递怎么理解?
层级
- 浅拷贝 只会将对象的各个属性进行依次复制,并不会进行递归复制,也就是说只会赋值目标对象的第一层属性。
- 深拷贝不同于浅拷贝,它不只拷贝目标对象的第一层属性,而是递归拷贝目标对象的所有属性。
是否为属性开辟新的堆
- 浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
- 深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
赋值,浅拷贝和深拷贝的区别
- 当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
- 浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。
- 深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。
var objOrigin = {
name: '耳东蜗牛',
information: {
age: 27,
base: 'shanghai'
}
}
// 赋值操作
var objAssignment = objOrigin;
// 浅拷贝
var objShallowCopy = Object.assign({}, objOrigin)
// 深拷贝
var objDeepCopy = JSON.parse(JSON.stringify(objOrigin))
内存查看
图示
浅拷贝
方式
数组
Array.concat
let array = [1, 2, {name: 'rodchen'}];
let arrayShallow = array.concat([]);
array[2].name = '耳东蜗牛';
console.log(arrayShallow);
Array.slice
let array = [1, 2, {name: 'rodchen'}];
let arrayShallow = array.slice(1);
array[2].name = '耳东蜗牛';
console.log(arrayShallow);
ES6延展符
let array = [1, 2, {name: 'rodchen'}];
let arrayShallow = [...array];
array[2].name = '耳东蜗牛';
console.log(arrayShallow);
Array.map | Array.filter
let array = [1, 2, {name: 'rodchen'}];
let arrayMapShallow = array.map(item => item);
let arrayFilterShallow = array.filter(item => true);
array[2].name = '耳东蜗牛';
console.log(arrayMapShallow);
console.log(arrayFilterShallow);
对象
Object.assign
let obj = {
age: 27,
information: {
name: 'rodchen'
}
}
let shallowObj = Object.assign({}, obj)
obj.information.name = '耳东蜗牛'
console.log(shallowObj)
对象延展符
let obj = {
age: 27,
information: {
name: 'rodchen'
}
}
let shallowObj = {...obj}
obj.information.name = '耳东蜗牛'
console.log(shallowObj)
手动实现
function cloneShallow(source) {
// 判断source是否为对象
if (!(source instanceof Object)) {
return source;
}
var target = source instanceof Array ? [] : {};
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
return target;
}
深拷贝
方式
JSON parse和stringify方法
- undefined、任意的函数、正则表达式类型以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时);
- 它会抛弃对象的constructor。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object;
- 如果对象中存在循环引用的情况无法正确处理。
手动实现
初始版本
function cloneDeep(source) {
// 判断source是否为对象
if (!(source instanceof Object)) {
return source;
}
var target = source instanceof Array ? [] : {};
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source instanceof Object ? cloneDeep(source[key]) : source[key];
}
}
return target;
}
let array = [1, 2, {name: 'rodchen'}];
let arrayShallow = cloneDeep(array);
array[2].name = '耳东蜗牛';
console.log(arrayShallow); // Array [1, 2, Object { name: "rodchen" }]
let obj = {
age: 27,
information: {
name: 'rodchen'
}
}
let shallowObj = cloneDeep(obj)
obj.information.name = '耳东蜗牛'
console.log(shallowObj) // Object { age: 27, information: Object { name: "rodchen" } }
循环引用
下面的例子当中,我们使用循环应用,执行上面的方法,汇报栈溢出。
let array = [1, 2];
array[2] = array
let arrayShallow = cloneDeep(array); // Error: Maximum call stack size exceeded
array[2].name = '耳东蜗牛';
console.log(arrayShallow);
let obj = {
age: 27,
}
obj.information = obj
let shallowObj = cloneDeep(obj) // Error: Maximum call stack size exceeded
obj.information.name = '耳东蜗牛'
console.log(shallowObj)
function cloneDeep(source, hash = new WeakMap()) {
// 判断source是否为对象
if (!(source instanceof Object)) {
return source;
}
if (hash.get(source)) return hash.get(source);
var target = source instanceof Array ? [] : {};
hash.set(source, target);
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source instanceof Object ? cloneDeep(source[key], hash) : source[key];
}
}
return target;
}
这里的weakMap可以通过ES5数组代替。
适配方法,日期,正则
function cloneDeep(source, hash = new WeakMap()) {
// 判断source是否为对象
if (!(source instanceof Object)) {
return source;
}
if (source instanceof Function) return new Function("return " + source.toString())();
if (source instanceof Date) return new Date(source);
if (source instanceof RegExp) return new RegExp(source);
if (hash.get(source)) return hash.get(source);
var target = source instanceof Array ? [] : {};
hash.set(source, target);
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source instanceof Object ? cloneDeep(source[key], hash) : source[key];
}
}
return target;
}
第三方
- Underscore —— _.clone():
这个方法实际上是一种浅复制 (shallow-copy)
- jQuery —— .extend():
在 jQuery 中也有这么一个叫 $.clone()的方法,可是它并不是用于一般的 JS 对象的深复制,而是用于 DOM 对象。
与 Underscore 类似,我们也是可以通过 $.extend() 方法来完成深复制。
值得庆幸的是,我们在 jQuery 中可以通过添加一个参数来实现递归extend。
调用$.extend(true, {}, ...)就可以实现深复制。
- lodash —— _.clone() / _.cloneDeep():
在lodash中关于复制的方法有两个,分别是_.clone()和_.cloneDeep()。
其中_.clone(obj, true)等价于_.cloneDeep(obj)
// npm install --save @jsmini/clone
import { clone, cloneJSON, cloneLoop, cloneForce } from '@jsmini/clone';
参照
segmentfault.com/a/119000001…
segmentfault.com/a/119000001…
jerryzou.com/posts/dive-…
juejin.cn/post/684490…