浅拷贝与深拷贝
浅拷贝与深拷贝
javascript中一般有按值传递和按引用传递两种复制方式:
按值传递的是基本数据类型(Number,String,Boolean,Null,Undefined,Symbol),一般存放于内存中的栈区,存取速度快,存放量小;
按引用传递的是引用类型(Object,Array,Function),一般存放与内存中的堆区,存取速度慢,存放量大,其引用指针存于栈区,并指向引用本身。
深拷贝和浅拷贝是相对于引用类型而言的:
-
浅拷贝是创建一个新对象,只复制一层对象的属性,并不包括对象里面的为引用类型的数据这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
-
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
深拷贝:将 B 对象拷贝到 A 对象中,包括 B 里面的子对象,
浅拷贝:将 B 对象拷贝到 A 对象中,但不包括 B 里面的子对象
var a1 = {b: {c: {}};
var a2 = shallowClone(a1); // 浅拷贝方法
a2.b.c === a1.b.c // true 新旧对象还是共享同一块内存
var a3 = deepClone(a3); // 深拷贝方法
a3.b.c === a1.b.c // false 新对象跟原对象不共享内存
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
赋值和深/浅拷贝的区别
三者的区别如下,不过比较的前提都是针对引用类型:
-
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
-
浅拷贝:重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象中的引用类型因共享同一块内存,会相互影响。
-
深拷贝:从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。
// 对象赋值
let obj1 = {
name : '浪里行舟',
arr : [1,[2,3],4],
};
let obj2 = obj1;
obj2.name = "阿浪";
obj2.arr[1] =[5,6,7] ;//基本数据类型和引用类型都会改变
//
console.log('obj1',obj1) // obj1 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj2',obj2) // obj2 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
//浅拷贝
let obj3=shallowClone(obj1)
obj3.name = "阿浪";
obj3.arr[1] = [5,6,7] ; // 新旧对象还是共享同一块内存
// 这是个浅拷贝的方法
function shallowClone(source) {//新建了target对象,故基本数据类型不会联动
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];//只拷贝了一层(拷贝的是地址),共享同一块内存
}
}
return target;
}
console.log('obj1',obj1) // obj1 { name: '浪里行舟', arr: [ 1, [ 5, 6, 7 ], 4 ] }
console.log('obj3',obj3) // obj3 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
//深拷贝
let obj4=deepClone(obj1)
obj4.name = "阿浪";
obj4.arr[1] = [5,6,7] ; // 新对象跟原对象不共享内存
// 这是个深拷贝的方法
function deepClone(obj) {
if (obj === null) return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (typeof obj !== "object") return obj;
let cloneObj = new obj.constructor();
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key]);
}
}
return cloneObj;
}
console.log('obj1',obj1) // obj1 { name: '浪里行舟', arr: [ 1, [ 2, 3 ], 4 ] }
console.log('obj4',obj4) // obj4 { name: '阿浪', arr: [ 1, [ 5, 6, 7 ], 4 ] }
obj1是原始对象,obj2是赋值操作得到的对象,obj3浅拷贝得到的对象,obj4深拷贝得到的对象,通过下面的表格,我们可以很清晰看到他们对原始数据的影响:
| -- | 和原数据是否指向同一对象 | 第一层数据为基本数据类型 | 原数据中包含子对象 |
|---|---|---|---|
| 赋值 | 是 | 改变会使原数据一同改变 | 改变会使原数据一同改变 |
| 浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变会使原数据一同改变 |
| 深拷贝 | 否 | 改变不会使原数据一同改变 | 改变不会使原数据一同改变 |
浅拷贝的实现方式
1.Object.assign()
let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' };
let obj2 = Object.assign({}, obj1);
obj2.person.name = "wade";
obj2.sports = 'football'
console.log(obj1); // { person: { name: 'wade', age: 41 }, sports: 'basketball' }
2.展开运算符...
let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }
深拷贝的实现方式
1.JSON.parse(JSON.stringify())
var obj = { a: {a: "hello"}, b: 33 };
var newObj = JSON.parse(JSON.stringify(obj));
newObj.b = "hello world";
console.log(obj); // { a: "hello", b: 33 };
console.log(newObj); // { a: "hello world", b: 33};
console.log(obj==newObj); // false
console.log(obj===newObj); // false
这也是利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则。
2.函数库lodash的_.cloneDeep方法
3.jQuery.extend()方法
使用递归思路实现了浅拷贝和深拷贝,第一个参数类型为Boolean,当为false的时候必须省略不写则是浅拷贝,当为true的时候为深拷贝:
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); // false