[id_8[id_[id_1542944348]66[id_1978312958]49257]9458560]
作者:前端工匠公号 / 浪里行舟君(本文来自作者投稿)
前言
在 JavaScript 编程语言中,存在多种复制对象的方式。若你对这门语言不够熟悉,复制对象时可能会遇到不少难题。那么,我们究竟该如何准确地复制一个对象呢?
读完本文,希望你能明白:
浅拷贝与深拷贝
var a1 = {b: {c: {}};
var[id_1443834190]// 浅拷贝方法
a2.b.c === a1.b.c // true 新旧对象还是共享同一块内存
var a3 = deepClone(a3); // 深拷贝方法
a3.b.c === a1.b.c // false 新对象跟原对象不共享内存
借助Conard Li大神的这两幅图示,我们能够更深入地把握它们所表达的核心思想。
总的来说,浅拷贝仅复制指向特定对象的指针,并不对对象本身进行复制;因此,新旧对象实际上仍占用同一内存区域。然而,深拷贝则会独立生成一个与原对象完全相同的副本,使得新对象与原对象不再共享内存空间;对副本的任何修改都不会影响到原对象。
赋值和深/浅拷贝的区别
这三者的区别如下,不过比较的前提都是针对引用类型:
首先,我们应当观察并比较在执行赋值操作或进行深/浅拷贝后,所得到的对象在经过修改时对原始对象所产生的作用:
// 对象赋值
let obj1 = {
name : '浪里行舟',
arr : [1,[2,3],4],
};
let obj2 = obj1;
obj2.name = "阿浪";
obj2.arr[1] =[5,6,7] ;
console.log('obj1',obj1)
对象obj1的属性包括名字“阿浪”以及一个数组arr,其中arr包含元素1,一个包含数字5、6、7的子数组,以及数字4。
console.log('obj2',obj2)
obj2 对象中包含的属性有:名字为“阿浪”,以及一个数组arr,该数组内依次有数字1、一个包含数字5、6、7的子数组,以及数字4。
// 浅拷贝
let obj1 = {
name : '浪里行舟',
arr : [1,[2,3],4],
};
let obj3=shallowClone(obj1)
obj3.name = "阿浪";
obj3.arr[1] = [5,6,7] ; // 新旧对象还是共享同一块内存
// 这是个浅拷贝的方法
function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
console.log('obj1',obj1)
obj1对象包含以下属性:名称为“浪里行舟”,并且有一个数组arr,该数组中包含元素1,一个包含5、6、7的子数组,以及元素4。
console.log('obj3',obj3)
obj3对象包含以下属性:名称为“阿浪”,以及一个数组arr,该数组内依次包含数字1、一个子数组[5, 6, 7]和数字4。
// 深拷贝
let obj1 = {
name : '浪里行舟',
arr : [1,[2,3],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对象中,将键值对设置为通过深度复制obj对象中的相应值。
}
}
return cloneObj;
}
console.log('obj1',obj1)
obj1对象中包含一个名为'浪里行舟'的属性,该属性是一个数组,数组内依次包含数字1、一个包含数字2和3的子数组以及数字4。
console.log('obj4',obj4)
obj4对象中,名称为“阿浪”,其内部包含一个数组arr,该数组由以下元素组成:数字1,一个包含数字5、6、7的子数组,以及数字4。
在上述示例中,obj1代表原始对象,obj2是通过赋值操作所获得的对象,obj3是通过浅拷贝方法得到的对象,而obj4则是通过深拷贝方法得到的对象。借助下方的表格,我们可以直观地观察到这些对象对原始数据所产生的作用:,,,。
浅拷贝的实现方式1.Object.assign()
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);
Wade,41岁,热爱篮球这项运动。
2.函数库lodash的_.clone方法
该函数库同样配备了_.clone方法,用于执行浅拷贝操作;关于如何使用该库实现深拷贝,我们将在后续内容中进行详细讲解。
var _ = require('lodash');
var obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.clone(obj1);
console.log(obj1.b.f === obj2.b.f);
// true
3.展开运算符…
展开运算符是ES6或ES2015的一项特性,它带来了一种极为便捷的手段用于执行浅拷贝操作,其功能与Object.assign()方法相似。
let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2)
obj2对象中包含以下信息:名字为“Kobe”,地址部分则由坐标x和y组成,具体数值分别为200和100。
4.Array.prototype.concat()
let arr = [1, 3, {
username: 'kobe'
}];
let arr2 = arr.concat();
arr2[2].username = 'wade';
console.log(arr);
不允许对用户名为“wade”的账户进行编辑操作。
5.Array.prototype.slice()
let arr = [1, 3, {
username: ' kobe'
}];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr);
禁止对用户名为“wade”的账户进行任何修改操作。
实现深拷贝的方法之一是利用JSON.parse()和JSON.stringify()函数。
let arr = [1, 3, {
username: ' kobe'
}];
创建变量arr4,通过JSON.parse()函数解析JSON.stringify()函数转换后的arr数组。
arr4[2].username = 'duncan';
console.log(arr, arr4)
这实际上是通过调用JSON.stringify函数将一个对象转换成了JSON格式的字符串,接着又使用JSON.parse函数将这个字符串转换回对象,这一转换过程不仅产生了新的对象,而且新对象还会创建一个新的栈空间,从而实现了对象的深度复制。
此方法虽能实现数组与对象的深度复制,然而它无法处理函数与正则表达式,因为经过JSON.stringify和JSON.parse的处理,原本的正则表达式会变成空对象,而函数则会变成null。
比如下面的例子:
let arr = [1, 3, {
username: ' kobe'
},function(){}];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'duncan';
console.log(arr, arr4)
2.函数库lodash的_.cloneDeep方法
此函数库同样配备了_.cloneDeep方法,专门用于执行深度复制操作。
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);
// false
3.jQuery.extend()方法
jQuery中内置了一个名为$.extend的方法,该功能可用于实现深拷贝操作。
$.extend(deepCopy, target, object1, [objectN]),其中第一个参数设置为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
4.手写递归方法
深度克隆的原理通过递归方法实现,具体做法是:首先遍历对象和数组,直至内部元素均为基本数据类型,接着进行复制操作,从而完成深度拷贝。
需留意的一种特定情形是对象间存在相互引用的现象,即对象的属性指向了自身。要处理这种循环引用的问题,我们可以增设一个专门的存储区域,用于记录当前对象与其拷贝对象之间的映射关系。在需要复制某个对象时,我们首先在存储区域中查询,看是否已经对该对象进行了拷贝。如果查询结果显示该对象已被拷贝,则直接返回该拷贝;若未找到,则继续执行复制操作,以此方法有效地解决了循环引用的难题。若对此处有所疑问,敬请详阅ConardLi大牛所撰写的《如何令面试官眼前一亮:深拷贝的撰写之道》一文。
function deepClone(obj, hash = new WeakMap()) {
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;
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj);
let cloneObj = new obj.constructor();
所探寻的是该类原型中的构造函数,而这个构造函数又指向了该类自身。
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
在cloneObj对象中,以key为键名,将obj对象中对应key的值进行深度复制,并将复制后的结果赋值给cloneObj[key]。
}
}
return cloneObj;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);
参考文章
本网站每日更新互联网创业教程,一年会员只需98,全站资源免费下载点击查看会员权益