在 ES6中使用扩展语法进行深度复制

我正在尝试为我的 Redux 项目创建一个深度拷贝映射方法,它将使用对象而不是数组。我读到,在 Redux 中,每个状态不应该改变以前状态的任何内容。

export const mapCopy = (object, callback) => {
return Object.keys(object).reduce(function (output, key) {


output[key] = callback.call(this, {...object[key]});


return output;
},
{});
}

它是有效的:

return mapCopy(state, e => {


if (e.id === action.id) {
e.title = 'new item';
}


return e;
})

然而,它不会深度复制内部项目,所以我需要调整它:

export const mapCopy = (object, callback) => {
return Object.keys(object).reduce(function (output, key) {
     

let newObject = {...object[key]};
newObject.style = {...newObject.style};
newObject.data = {...newObject.data};


output[key] = callback.call(this, newObject);


return output;
}, {});
}

这就不那么优雅了,因为它需要知道传递了哪些对象。 ES6中有没有使用扩展语法深度复制对象的方法?

195499 次浏览

ES6没有内置这样的功能。我认为你有几个选择,这取决于你想做什么。

如果你真的想深度复制:

  1. 使用库。例如,loash 有一个 cloneDeep方法。
  2. 实现您自己的克隆功能。

特定问题的替代解决方案(无深拷贝)

然而,我认为,如果你愿意改变一些东西,你可以为自己节省一些工作。我假设你控制所有的调用网站到你的功能。

  1. 指定传递给 mapCopy的所有回调必须返回新对象,而不是变更现有对象。例如:

    mapCopy(state, e => {
    if (e.id === action.id) {
    return Object.assign({}, e, {
    title: 'new item'
    });
    } else {
    return e;
    }
    });
    

    这将使用 Object.assign创建一个新对象,在该新对象上设置 e的属性,然后在该新对象上设置一个新标题。这意味着永远不要对现有对象进行变异,只在必要时创建新对象。

  2. mapCopy现在可以非常简单:

    export const mapCopy = (object, callback) => {
    return Object.keys(object).reduce(function (output, key) {
    output[key] = callback.call(this, object[key]);
    return output;
    }, {});
    }
    

Essentially, mapCopy is trusting its callers to do the right thing. This is why I said this assumes you control all call sites.

MDN 的

注意: 复制数组时,扩展语法有效地深入了一个层次。因此,它可能不适合复制多维数组,如下面的示例所示(Object.sign ()和传播语法也是如此)。

我个人建议使用 Lodash 的克隆人 Deep函数进行多级对象/数组克隆。

下面是一个可行的例子:

const arr1 = [{ 'a': 1 }];


const arr2 = [...arr1];


const arr3 = _.clone(arr1);


const arr4 = arr1.slice();


const arr5 = _.cloneDeep(arr1);


const arr6 = [...{...arr1}]; // a bit ugly syntax but it is working!




// first level
console.log(arr1 === arr2); // false
console.log(arr1 === arr3); // false
console.log(arr1 === arr4); // false
console.log(arr1 === arr5); // false
console.log(arr1 === arr6); // false


// second level
console.log(arr1[0] === arr2[0]); // true
console.log(arr1[0] === arr3[0]); // true
console.log(arr1[0] === arr4[0]); // true
console.log(arr1[0] === arr5[0]); // false
console.log(arr1[0] === arr6[0]); // false
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>

使用 JSON进行深度复制

var newObject = JSON.parse(JSON.stringify(oldObject))

var oldObject = {
name: 'A',
address: {
street: 'Station Road',
city: 'Pune'
}
}
var newObject = JSON.parse(JSON.stringify(oldObject));


newObject.address.city = 'Delhi';
console.log('newObject');
console.log(newObject);
console.log('oldObject');
console.log(oldObject);

function deepclone(obj) {
let newObj = {};


if (typeof obj === 'object') {
for (let key in obj) {
let property = obj[key],
type = typeof property;
switch (type) {
case 'object':
if( Object.prototype.toString.call( property ) === '[object Array]' ) {
newObj[key] = [];
for (let item of property) {
newObj[key].push(this.deepclone(item))
}
} else {
newObj[key] = deepclone(property);
}
break;
default:
newObj[key] = property;
break;


}
}
return newObj
} else {
return obj;
}
}

我经常这样说:

function deepCopy(obj) {
if(typeof obj !== 'object' || obj === null) {
return obj;
}


if(obj instanceof Date) {
return new Date(obj.getTime());
}


if(obj instanceof Array) {
return obj.reduce((arr, item, i) => {
arr[i] = deepCopy(item);
return arr;
}, []);
}


if(obj instanceof Object) {
return Object.keys(obj).reduce((newObj, key) => {
newObj[key] = deepCopy(obj[key]);
return newObj;
}, {})
}
}
// use: clone( <thing to copy> ) returns <new copy>
// untested use at own risk
function clone(o, m){
// return non object values
if('object' !==typeof o) return o
// m: a map of old refs to new object refs to stop recursion
if('object' !==typeof m || null ===m) m =new WeakMap()
var n =m.get(o)
if('undefined' !==typeof n) return n
// shallow/leaf clone object
var c =Object.getPrototypeOf(o).constructor
// TODO: specialize copies for expected built in types i.e. Date etc
switch(c) {
// shouldn't be copied, keep reference
case Boolean:
case Error:
case Function:
case Number:
case Promise:
case String:
case Symbol:
case WeakMap:
case WeakSet:
n =o
break;
// array like/collection objects
case Array:
m.set(o, n =o.slice(0))
// recursive copy for child objects
n.forEach(function(v,i){
if('object' ===typeof v) n[i] =clone(v, m)
});
break;
case ArrayBuffer:
m.set(o, n =o.slice(0))
break;
case DataView:
m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.byteLength))
break;
case Map:
case Set:
m.set(o, n =new (c)(clone(Array.from(o.entries()), m)))
break;
case Int8Array:
case Uint8Array:
case Uint8ClampedArray:
case Int16Array:
case Uint16Array:
case Int32Array:
case Uint32Array:
case Float32Array:
case Float64Array:
m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.length))
break;
// use built in copy constructor
case Date:
case RegExp:
m.set(o, n =new (c)(o))
break;
// fallback generic object copy
default:
m.set(o, n =Object.assign(new (c)(), o))
// recursive copy for child objects
for(c in n) if('object' ===typeof n[c]) n[c] =clone(n[c], m)
}
return n
}
const cloneData = (dataArray) => {
newData= []
dataArray.forEach((value) => {
newData.push({...value})
})
return newData
}
  • A = [{ name: “ siva”} ,{ name: “ siva1”}] ;
  • B = myCopy (a)
  • 错误

我自己在昨天得到了这些答案,试图找到一种深度复制复杂结构的方法,其中可能包括递归链接。由于我对以前提出的任何建议都不满意,我自己实现了这个轮子。而且效果很好。希望能帮到别人。

示例用法:

OriginalStruct.deep_copy = deep_copy; // attach the function as a method


TheClone = OriginalStruct.deep_copy();

请查看 https://github.com/latitov/JS_DeepCopy的实际例子如何使用它,还有 deep _ print ()。

如果你很快就需要它,下面就是 deep _ copy ()函数的源代码:

function deep_copy() {
'use strict';   // required for undef test of 'this' below


// Copyright (c) 2019, Leonid Titov, Mentions Highly Appreciated.


var id_cnt = 1;
var all_old_objects = {};
var all_new_objects = {};
var root_obj = this;


if (root_obj === undefined) {
console.log(`deep_copy() error: wrong call context`);
return;
}


var new_obj = copy_obj(root_obj);


for (var id in all_old_objects) {
delete all_old_objects[id].__temp_id;
}


return new_obj;
//


function copy_obj(o) {
var new_obj = {};
if (o.__temp_id === undefined) {
o.__temp_id = id_cnt;
all_old_objects[id_cnt] = o;
all_new_objects[id_cnt] = new_obj;
id_cnt ++;


for (var prop in o) {
if (o[prop] instanceof Array) {
new_obj[prop] = copy_array(o[prop]);
}
else if (o[prop] instanceof Object) {
new_obj[prop] = copy_obj(o[prop]);
}
else if (prop === '__temp_id') {
continue;
}
else {
new_obj[prop] = o[prop];
}
}
}
else {
new_obj = all_new_objects[o.__temp_id];
}
return new_obj;
}
function copy_array(a) {
var new_array = [];
if (a.__temp_id === undefined) {
a.__temp_id = id_cnt;
all_old_objects[id_cnt] = a;
all_new_objects[id_cnt] = new_array;
id_cnt ++;


a.forEach((v,i) => {
if (v instanceof Array) {
new_array[i] = copy_array(v);
}
else if (v instanceof Object) {
new_array[i] = copy_object(v);
}
else {
new_array[i] = v;
}
});
}
else {
new_array = all_new_objects[a.__temp_id];
}
return new_array;
}
}

干杯@!

const a = {
foods: {
dinner: 'Pasta'
}
}
let b = JSON.parse(JSON.stringify(a))
b.foods.dinner = 'Soup'
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Pasta

使用 JSON.stringifyJSON.parse是最好的方法。因为通过使用扩展运算符,当 json 对象包含其中的另一个对象时,我们不会得到有效的答案。我们需要手动指定。

下面是 DeepClone 函数,它处理所有基元、数组、对象和函数数据类型

function deepClone(obj){
if(Array.isArray(obj)){
var arr = [];
for (var i = 0; i < obj.length; i++) {
arr[i] = deepClone(obj[i]);
}
return arr;
}


if(typeof(obj) == "object"){
var cloned = {};
for(let key in obj){
cloned[key] = deepClone(obj[key])
}
return cloned;
}
return obj;
}


console.log( deepClone(1) )


console.log( deepClone('abc') )


console.log( deepClone([1,2]) )


console.log( deepClone({a: 'abc', b: 'def'}) )


console.log( deepClone({
a: 'a',
num: 123,
func: function(){'hello'},
arr: [[1,2,3,[4,5]], 'def'],
obj: {
one: {
two: {
three: 3
}
}
}
}) ) 

这是我的深度拷贝算法。

const DeepClone = (obj) => {
if(obj===null||typeof(obj)!=='object')return null;
let newObj = { ...obj };


for (let prop in obj) {
if (
typeof obj[prop] === "object" ||
typeof obj[prop] === "function"
) {
newObj[prop] = DeepClone(obj[prop]);
}
}


return newObj;
};

我建议使用扩展运算符。如果你需要更新第二层,你需要第二次展开。如果 oldObject 中不存在地址,那么尝试使用类似 newObject.address.city的方法更新 newObject 将会抛出一个错误。

const oldObject = {
name: 'A',
address: {
street: 'Station Road',
city: 'Pune'
}
}


const newObject = {
...oldObject,
address: {
...oldObject.address,
city: 'Delhi'
}
}


console.log(newObject)

你可以像下面这样使用 structuredClone():

const myOriginal = {
title: "Full Stack JavaScript Developer",
info: {
firstname: "Abolfazl",
surname: "Roshanzamir",
age: 34
}
};
const myDeepCopy = structuredClone(myOriginal);

StructuredClone ()

可以使用 structuredClone(),它是 深度拷贝内置的函数。 结构化克隆解决了 JSON.stringify()技术的许多(尽管不是全部)缺点。 结构化克隆可以处理循环数据结构, 支持许多内置的数据类型,通常更健壮,通常更快。

然而,它仍然有一些局限性,可能会让你措手不及:

1-原型机: 如果对类实例使用 structuredClone(), 您将得到一个普通对象作为返回值,因为结构化克隆丢弃了对象的原型链。

2-职能: 如果您的对象包含函数,它们将被悄悄地丢弃。

3-非克隆人: 有些值不是结构化可克隆的,最明显的是 Error 和 DOM 节点。它会导致 structuredClone ()抛出。

const myDeepCopy = structuredClone(myOriginal);

JSON.stringify

如果只是想将对象深度复制到另一个对象, 您所需要做的就是 JSON.stringify对象,然后使用 JSON.parse解析它。 这实际上将执行对象的深度复制。

let user1 = {
name: 'Abolfazl Roshanzamir',
age: 34,
university: {
name: 'Shiraz Bahonar University'
}
};




let user2 = JSON.parse(JSON.stringify(user1));


user2.name = 'Andy Madadian';
user2.university.name = 'Kerman Bahonar University'


console.log(user2);
// { name: 'Andy Madadian', age: 33, university: { name: 'Kerman Bahonar University' } }


console.log(user1);
// { name: 'Abolfazl Roshanzamir', age: 33, university: { name: 'Shiraz Bahonar University' } }

传播操作符/Object.sign ()

使用对象扩展操作符或 Object.assign()在 JavaScript 中创建浅拷贝的一种方法如下所示:

const myShallowCopySpread = {...myOriginal};
const myShallowCopyObjectAssign=Object.assign({},obj)

表演

在性能方面,创建者 Surma 指出,对于小型对象,JSON.Parse()可以更快一些。但是当你有一个大的对象,复杂的对象 structuredClone()开始变得明显更快。

浏览器支持 非常棒,甚至 Node.js也支持。

这是一个非常古老的问题,但我认为在2022年有许多方法可以解决这个问题。然而,如果你想要一个简单、快速、普通的 JS 解决方案,可以试试这个:

const cloner = (o) => {
let idx = 1
const isArray = (a) => a instanceof Array
const isObject = (o) => o instanceof Object
const isUndefined = (a) => a === undefined
const process = v => {
if (isArray(v)) return cloneArray(v)
else if (isObject(v)) return cloneObject(v)
else return v
}
const register = (old, o) => {
old.__idx = idx
oldObjects[idx] = old
newObjects[idx] = o
idx++
}
const cloneObject = o => {
if (!isUndefined(o.__idx)) return newObjects[o.__idx]


const obj = {}
for (const prop in o) {
if (prop === '__idx') continue
obj[prop] = process(o[prop])
}
register(o, obj)


return obj
}
const cloneArray = a => {
if (!isUndefined(a.__idx)) return newObjects[a.__idx]


const arr = a.map((v) => process(v))
register(a, arr)


return arr
}
const oldObjects = {}
const newObjects = {}


let tmp
if (isArray(o)) tmp = cloneArray(o)
else if (isObject(o)) tmp = cloneObject(o)
else return o


for (const id in oldObjects) delete oldObjects[id].__idx


return tmp
}


const c = {
id: 123,
label: "Lala",
values: ['char', 1, {flag: true}, [1,2,3,4,5], ['a', 'b']],
name: undefined
}


const d = cloner(c)
d.name = "Super"
d.values[2].flag = false
d.values[3] = [6,7,8]
console.log({ c, d })

它是递归和自包含的,所有需要的函数都在 function cloner()中定义。

在这个代码片段中,我们正在处理 ArrayObject类型,如果你想添加更多的处理程序,你可以添加指定的处理程序,如 Date和克隆它,如 new Date(v.getTime())

对我来说,ArrayObject是我在实现中使用最多的类型。