[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大神的这两幅图示,我们能够更深入地把握它们所表达的核心思想。

浅拷贝与深拷贝区别_javascript对象复制方法_js深拷贝和浅拷贝的区别

js深拷贝和浅拷贝的区别_javascript对象复制方法_浅拷贝与深拷贝区别

总的来说,浅拷贝仅复制指向特定对象的指针,并不对对象本身进行复制;因此,新旧对象实际上仍占用同一内存区域。然而,深拷贝则会独立生成一个与原对象完全相同的副本,使得新对象与原对象不再共享内存空间;对副本的任何修改都不会影响到原对象。

赋值和深/浅拷贝的区别

这三者的区别如下,不过比较的前提都是针对引用类型:

首先,我们应当观察并比较在执行赋值操作或进行深/浅拷贝后,所得到的对象在经过修改时对原始对象所产生的作用:

// 对象赋值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则是通过深拷贝方法得到的对象。借助下方的表格,我们可以直观地观察到这些对象对原始数据所产生的作用:,,,。

javascript对象复制方法_js深拷贝和浅拷贝的区别_浅拷贝与深拷贝区别

浅拷贝的实现方式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)

js深拷贝和浅拷贝的区别_浅拷贝与深拷贝区别_javascript对象复制方法

这实际上是通过调用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)

js深拷贝和浅拷贝的区别_浅拷贝与深拷贝区别_javascript对象复制方法

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,全站资源免费下载点击查看会员权益

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注