今天,在阅读《红宝书》第8章的理解对象的"合并对象"时,发现书中提到了:Object.assign()实际上对每个源对象执行的是浅复制。
这也是面试高频知识点,现整理下关于JavaScript
的深拷贝与浅拷贝。
预备知识
-
在你想要了解深拷贝与浅拷贝的时候,你知道:
JavaScript
中的数据类型有哪些?- 这些类型是如何进行存储的?
-
如果你知道这些,你的基础还不错;不知道或知道的不全,也没关系,现在学起来不晚,加油。
-
搜索关键字
JS的数据类型
、JS数据类型的存储方式
去学习吧,也可参考我的笔记。 -
还可以参考 JS的深拷贝和浅拷贝、详解JS深拷贝与浅拷贝中的相关介绍,本文不再赘述了。
初识概念
- 以下几种不同的解释,仅供参考与理解。
- 如何区分深拷贝与浅拷贝,简单点来说,就是假设B复制了A,当修改A时,看B是否会发生变化,如果B也跟着变了,说明这是浅拷贝,拿人手短,如果B没变,那就是深拷贝,自食其力。
- 深拷贝和浅拷贝都只针对引用数据类型,浅拷贝会对对象逐个成员依次拷贝,但只复制内存地址,而不复制对象本身,新旧对象成员还是共享同一内存;深拷贝会另外创建一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
区别
:浅拷贝只复制对象的第一层属性,而深拷贝会对对象的属性进行递归复制。 我的理解
:如果一个对象Obj2
复制了Obj1
,当修改Obj2
的时候,Obj1
也发生了变化,就是浅拷贝
;如果Obj1没有发生变化,那么就是深拷贝
。// 1 定义一个Obj1对象 var obj1 = { name: "张三"; age: 20; }; // 2 将obj1赋值给boj2 var obj2 = obj1; // 3 修改obj2 obj2.name= "李四"; console.log(obj1.name); // 李四 console.log(obj2.name); // 李四 你会发现,两个对象的name都变了,所以这是什么拷贝? 没错,这个是浅拷贝,obj2改变,obj1也变化了。
浅拷贝
- 常见的浅拷贝有:
- Object.assign()
- Array.prototype.concat()
- Array.prototype.slice()
- 扩展运算符
- ☆手写浅拷贝
Object.assign()
ES6提供了Object.assign()
方法,这个方法接收一个目标对象和一个或多个源对象作为参数,将所有可枚举的自有属性从一个或多个源对象复制到目标对象,返回修改后的对象。
let dest, src, result;
/** 简单复制 */
dest = {};
src = { id: 'src' };
result = Object.assign(dest, src);
// Object.assign 修改目标对象,也会返回修改后的目标对象
console.log(dest === result); // true
console.log(dest !== src); // true
console.log(result); // { id: src }
console.log(dest); // { id: src }
/** 多个源对象 */
dest = {};
result = Object.assign(dest, { a: 'foo' }, { b: 'bar' });
console.log(result); // { a: foo, b: bar }
Array.prototype.concat()
concat()
方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
const arr1 = [1,2,3, {a: 4}]
const arr2 = arr1.concat([5])
arr2[0] = 2022
arr2[3].a = '你好世界'
console.log(arr1) // [1, 2, 3, Object { a: "你好世界" }]
console.log(arr2) // [2022, 2, 3, Object { a: "你好世界" }, 5]
// 如果复制的数组是一层,看似是深拷贝;但其实改方法是浅拷贝,复制多层就会发现。
Array.prototype.slice()
slice()
方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝
(包括 begin,不包括end)。原始数组不会被改变。
const arr1 = [1,2,3, {a: 4},5,6]
const arr2 = arr1.slice([1])
arr2[0] = 2022
arr2[2].a = '你好世界'
console.log(arr1) // [1, 2, 3, Object { a: "你好世界" }, 5, 6]
console.log(arr2) // [2022, 3, Object { a: "你好世界" }, 5, 6]
扩展运算符
const arr1 = [1,2,3, {a: 4},5,6]
const arr2 = [...arr1]
arr2[0] = 2022
arr2[3].a = '你好世界'
console.log(arr1) // [1, 2, 3, Object { a: "你好世界" }, 5, 6]
console.log(arr2) // [2022, 2, 3, Object { a: "你好世界" }, 5, 6]
☆手写浅拷贝函数
// 手写浅拷贝
function shallowCopy(obj1) {
let obj2 = Array.isArray(obj1) ? [] : {};
for(let i in obj1) {
obj2[i] = obj1[i]
}
return obj2
}
const obj1 = {
name: '张三',
age: 20,
list: [{item: '中国'},1,2,3,4]
}
const obj2 = shallowCopy(obj1)
obj2.name = '李四'
obj2.list[0].item = '祖国万岁'
console.log(obj1, 'obj1')
console.log(obj2, 'obj2')
深拷贝
- 常见的深拷贝
- JSON.stringify()
- ☆手写深拷贝(递归函数)
JSON.stringify()
原理:用JSON.stringify()将对象转成字符串,再用JSON.parse()把字符串解析成对象。
var obj1 = {
name: '张三',
list : [{item: '中国'},1,2,3,4]
};
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.name = "李四";
obj2.list[0].item = '祖国万岁'
console.log('obj1',obj1)
console.log('obj2',obj2)
☆手写深拷贝函数
- 基础版
function deepCopy(obj){
var result= Array.isArray(obj) ? [] : {}
if (obj && typeof(obj) === 'object') {
for (let i in obj) {
if (obj.hasOwnProperty(i)){ // 思考:这句是否有必要?
if (obj[i] && typeof(obj[i]) === 'object') {
result[i] = deepCopy(obj[i])
} else {
result[i] = obj[i]
}
}
}
}
return result
}
var obj1 = {
a: 1,
b: {
c: 2
}
};
var obj2 = deepCopy(obj1);
obj2.a = '一';
obj2.b.c = '二'
console.log('obj1', obj1)
console.log('obj2', obj2)
- 进阶版(防止循环递归爆栈)
function deepCopy(obj, parent = null) {
let result = Array.isArray(obj) ? [] : {}
let _parent = parent
// 该字段有父级则需要追溯该字段的父级
while(_parent) {
// 如果该字段引用了它的父级,则为循环引用
if (_parent.originalParent === obj) {
// 循环引用返回同级的新对象
return _parent.currentParent
}
_parent = _parent.parent
}
if (obj && typeof(obj) === 'object') {
for (let i in obj) {
// 如果字段的值也是一个对象
if (obj[i] && typeof(obj[i]) === 'object') {
// 递归执行深拷,将同级的待拷贝对象传递给parent,方便追溯循环引用
result[i] = deepCopy(obj[i], {
originalParent: obj,
currentParent: result,
parent: parent
})
} else {
result[i] = obj[i]
}
}
}
return result
}
var obj1 = {
x: 1,
y: 2
};
obj1.z = obj1
var obj2 = deepCopy(obj1)
console.log('obj1', obj1)
console.log('obj2', obj2)
- 终极版(支持基本数据类型、Array、Object、原型链、RegExp、Date类型)
function deepCopy(obj, parent = null) {
let result
let _parent = parent
while(_parent) {
if (_parent.originalParent === obj) {
return _parent.currentParent
}
_parent = _parent.parent
}
if (obj && typeof(obj) === 'object') {
if (obj instanceof RegExp) {
result = new RegExp(obj.source, obj.flags)
} else if (obj instanceof Date) {
result = new Date(obj.getTime())
} else {
if (obj instanceof Array) {
result = []
} else {
let proto = Object.getPrototypeOf(obj)
result = Object.create(proto)
}
for (let i in obj) {
if(obj[i] && typeof(obj[i]) === 'object') {
result[i] = deepCopy(obj[i], {
originalParent: obj,
currentParent: result,
parent: parent
})
} else {
result[i] = obj[i]
}
}
}
} else {
return obj
}
return result
}
var obj1 = {
x: 1
}
//试调用
function construct(){
this.a = 1,
this.b = {
x:2,
y:3,
z:[4,5,[6]]
},
this.c = [7,8,[9,10]],
this.d = new Date(),
this.e = /abc/ig,
this.f = function(a,b){
return a+b
},
this.g = null,
this.h = undefined,
this.i = "hello",
this.j = Symbol("foo")
}
construct.prototype.str = "I'm prototype"
var obj1 = new construct()
obj1.k = obj1
obj2 = deepCopy(obj1)
obj2.b.x = 999
obj2.c[0] = 666
console.log('obj1', obj1)
console.log('obj2', obj2)
其他方法
_.cloneDeep()
使用lodash
的_.cloneDeep()
方法,可以实现深克隆。
const _ = require('lodash');
let oldObj = {
a: 1, b: 2,c:{value:3}
}
let newObj = _.cloneDeep(oldObj)
newObj.a = 2
newObj.c.value = 4
console.log(oldObj) // { a: 1, b: 2, c: { value: 3 } }
console.log(newObj) // { a: 2, b: 2, c: { value: 4 } }
$.extend()
jQuery
提供的方法:$.extend()
let a=[0,1,[2,3],4],
b=$.extend(true,[],a);
a[0]=1;
a[2][0]=1;
console.log(a,b);