如何正确克隆 JavaScript 对象?

Posted

技术标签:

【中文标题】如何正确克隆 JavaScript 对象?【英文标题】:How do I correctly clone a JavaScript object? 【发布时间】:2010-10-18 05:17:24 【问题描述】:

我有一个对象x。我想将它复制为对象y,这样对y 的更改就不会修改x。我意识到复制从内置 javascript 对象派生的对象会导致额外的、不需要的属性。这不是问题,因为我正在复制我自己的文字构造对象之一。

【问题讨论】:

看到这个问题:***.com/questions/122102/… 对于 JSON,我使用 mObj=JSON.parse(JSON.stringify(jsonObject)); 我真的不明白为什么没有人建议Object.create(o),它可以满足作者的所有要求? var x = deep: key: 1 ; var y = Object.create(x); x.deep.key = 2; 执行此操作后,y.deep.key 也将为 2,因此 Object.create 不能用于克隆... @r3wt 这不起作用...请仅在对解决方案进行基本测试后发布.. 【参考方案1】:

为了更好地理解对象的复制,this illustrative jsbin may be of value

class base 
  get under()return true


class a extends base 

const b = 
  get b1()return true,
  b: true


console.log('Object assign')
let t1 = Object.create(b)
t1.x = true
const c = Object.assign(t1, new a())
console.log(c.b1 ? 'prop value copied': 'prop value gone')
console.log(c.x ? 'assigned value copied': 'assigned value gone')
console.log(c.under ? 'inheritance ok': 'inheritance gone')
console.log(c.b1 ? 'get value unchanged' : 'get value lost')
c.b1 = false
console.log(c.b1? 'get unchanged' : 'get lost')
console.log('-----------------------------------')
console.log('Object assign  - order swopped')
t1 = Object.create(b)
t1.x = true
const d = Object.assign(new a(), t1)
console.log(d.b1 ? 'prop value copied': 'prop value gone')
console.log(d.x ? 'assigned value copied': 'assigned value gone')
console.log(d.under ? 'inheritance n/a': 'inheritance gone')
console.log(d.b1 ? 'get value copied' : 'get value lost')
d.b1 = false
console.log(d.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('Spread operator')
t1 = Object.create(b)
t2 = new a()
t1.x = true
const e =  ...t1, ...t2 
console.log(e.b1 ? 'prop value copied': 'prop value gone')
console.log(e.x ? 'assigned value copied': 'assigned value gone')
console.log(e.under ? 'inheritance ok': 'inheritance gone')
console.log(e.b1 ? 'get value copied' : 'get value lost')
e.b1 = false
console.log(e.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('Spread operator on getPrototypeOf')
t1 = Object.create(b)
t2 = new a()
t1.x = true
const e1 =  ...Object.getPrototypeOf(t1), ...Object.getPrototypeOf(t2) 
console.log(e1.b1 ? 'prop value copied': 'prop value gone')
console.log(e1.x ? 'assigned value copied': 'assigned value gone')
console.log(e1.under ? 'inheritance ok': 'inheritance gone')
console.log(e1.b1 ? 'get value copied' : 'get value lost')
e1.b1 = false
console.log(e1.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('keys, defineProperty, getOwnPropertyDescriptor')
f = Object.create(b)
t2 = new a()
f.x = 'a'
Object.keys(t2).forEach(key=> 
  Object.defineProperty(f,key,Object.getOwnPropertyDescriptor(t2, key))
)
console.log(f.b1 ? 'prop value copied': 'prop value gone')
console.log(f.x ? 'assigned value copied': 'assigned value gone')
console.log(f.under ? 'inheritance ok': 'inheritance gone')
console.log(f.b1 ? 'get value copied' : 'get value lost')
f.b1 = false
console.log(f.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')
console.log('defineProperties, getOwnPropertyDescriptors')
let g = Object.create(b)
t2 = new a()
g.x = 'a'
Object.defineProperties(g,Object.getOwnPropertyDescriptors(t2))
console.log(g.b1 ? 'prop value copied': 'prop value gone')
console.log(g.x ? 'assigned value copied': 'assigned value gone')
console.log(g.under ? 'inheritance ok': 'inheritance gone')
console.log(g.b1 ? 'get value copied' : 'get value lost')
g.b1 = false
console.log(g.b1? 'get copied' : 'get lost')
console.log('-----------------------------------')

【讨论】:

【参考方案2】:

使用defaults(历史上特定于nodejs,但由于现代JS,现在可以从浏览器中使用):

import defaults from 'object.defaults';

const myCopy = defaults(, myObject);

【讨论】:

【参考方案3】:

据 Airbnb JavaScript Style Guide 报道,共有 404 位贡献者:

对象扩展运算符优于 Object.assign 而非浅拷贝 对象。使用对象剩余操作符获取具有特定条件的新对象 属性省略。

// very bad
const original =  a: 1, b: 2 ;
const copy = Object.assign(original,  c: 3 ); // this mutates `original` ಠ_ಠ
delete copy.a; // so does this

// bad
const original =  a: 1, b: 2 ;
const copy = Object.assign(, original,  c: 3 ); // copy =>  a: 1, b: 2, c: 3 

// good
const original =  a: 1, b: 2 ;
const copy =  ...original, c: 3 ; // copy =>  a: 1, b: 2, c: 3 

const  a, ...noA  = copy; // noA =>  b: 2, c: 3 

另外我想警告你,尽管 Airbnb 几乎不推荐对象传播运算符方法。请记住,Microsoft Edge 仍然不支持此 2018 功能。

ES2016+ Compat table >>

【讨论】:

【参考方案4】:

互联网上的大多数解决方案都存在几个问题。所以我决定做一个跟进,其中包括为什么不应该接受已接受的答案。

开始情况

我想深度复制 Javascript Object 及其所有子级及其子级等等。但由于我不是一个普通的开发者,我的Object正常 propertiescircular structures 甚至nested objects

所以让我们先创建一个circular structure 和一个nested object

function Circ() 
    this.me = this;


function Nested(y) 
    this.y = y;

让我们将所有内容放在一个名为 aObject 中。

var a = 
    x: 'a',
    circ: new Circ(),
    nested: new Nested('a')
;

接下来,我们要将a 复制到名为b 的变量中并对其进行变异。

var b = a;

b.x = 'b';
b.nested.y = 'b';

您知道这里发生了什么,否则您甚至不会想到这个好问题。

console.log(a, b);

a --> Object 
    x: "b",
    circ: Circ 
        me: Circ  ... 
    ,
    nested: Nested 
        y: "b"
    


b --> Object 
    x: "b",
    circ: Circ 
        me: Circ  ... 
    ,
    nested: Nested 
        y: "b"
    

现在让我们找到解决方案。

JSON

我尝试的第一次尝试是使用JSON

var b = JSON.parse( JSON.stringify( a ) );

b.x = 'b';
b.nested.y = 'b';

不要在上面浪费太多时间,你会得到TypeError: Converting circular structure to JSON

递归复制(接受的“答案”)

让我们看看接受的答案。

function cloneSO(obj) 
    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) 
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    

    // Handle Array
    if (obj instanceof Array) 
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) 
            copy[i] = cloneSO(obj[i]);
        
        return copy;
    

    // Handle Object
    if (obj instanceof Object) 
        var copy = ;
        for (var attr in obj) 
            if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
        
        return copy;
    

    throw new Error("Unable to copy obj! Its type isn't supported.");

看起来不错,嗯?它是对象的递归副本,也可以处理其他类型,例如 Date,但这不是必需的。

var b = cloneSO(a);

b.x = 'b';
b.nested.y = 'b';

递归和circular structures 不能很好地协同工作...RangeError: Maximum call stack size exceeded

原生解决方案

在与我的同事争论之后,我的老板问我们发生了什么事,他在谷歌搜索后找到了一个简单的解决方案。它叫做Object.create

var b = Object.create(a);

b.x = 'b';
b.nested.y = 'b';

这个解决方案是前段时间添加到 Javascript 中的,甚至可以处理 circular structure

console.log(a, b);

a --> Object 
    x: "a",
    circ: Circ 
        me: Circ  ... 
    ,
    nested: Nested 
        y: "b"
    


b --> Object 
    x: "b",
    circ: Circ 
        me: Circ  ... 
    ,
    nested: Nested 
        y: "b"
    

...你看,它不适用于内部的嵌套结构。

原生解决方案的 polyfill

在旧版浏览器中,Object.create 有一个 polyfill,就像 IE 8 一样。这就像 Mozilla 推荐的那样,当然,它并不完美,并且会导致与 本机解决方案相同的问题.

function F() ;
function clonePF(o) 
    F.prototype = o;
    return new F();


var b = clonePF(a);

b.x = 'b';
b.nested.y = 'b';

我已将F 放在范围之外,以便我们查看instanceof 告诉我们的内容。

console.log(a, b);

a --> Object 
    x: "a",
    circ: Circ 
        me: Circ  ... 
    ,
    nested: Nested 
        y: "b"
    


b --> F 
    x: "b",
    circ: Circ 
        me: Circ  ... 
    ,
    nested: Nested 
        y: "b"
    


console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> true

原生解决方案相同的问题,但输出稍差。

更好(但不完美)的解决方案

在四处挖掘时,我发现了一个与此问题类似的问题 (In Javascript, when performing a deep copy, how do I avoid a cycle, due to a property being "this"?),但有一个更好的解决方案。

function cloneDR(o) 
    const gdcc = "__getDeepCircularCopy__";
    if (o !== Object(o)) 
        return o; // primitive value
    

    var set = gdcc in o,
        cache = o[gdcc],
        result;
    if (set && typeof cache == "function") 
        return cache();
    
    // else
    o[gdcc] = function()  return result; ; // overwrite
    if (o instanceof Array) 
        result = [];
        for (var i=0; i<o.length; i++) 
            result[i] = cloneDR(o[i]);
        
     else 
        result = ;
        for (var prop in o)
            if (prop != gdcc)
                result[prop] = cloneDR(o[prop]);
            else if (set)
                result[prop] = cloneDR(cache);
    
    if (set) 
        o[gdcc] = cache; // reset
     else 
        delete o[gdcc]; // unset again
    
    return result;


var b = cloneDR(a);

b.x = 'b';
b.nested.y = 'b';

让我们看看输出...

console.log(a, b);

a --> Object 
    x: "a",
    circ: Object 
        me: Object  ... 
    ,
    nested: Object 
        y: "a"
    


b --> Object 
    x: "b",
    circ: Object 
        me: Object  ... 
    ,
    nested: Object 
        y: "b"
    


console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> false

要求匹配,但还是有一些小问题,包括将nestedcircinstance改为Object

共享一个叶子的树的结构不会被复制,它们会成为两个独立的叶子:

        [Object]                     [Object]
         /    \                       /    \
        /      \                     /      \
      |/_      _\|                 |/_      _\|  
  [Object]    [Object]   ===>  [Object]    [Object]
       \        /                 |           |
        \      /                  |           |
        _\|  |/_                 \|/         \|/
        [Object]               [Object]    [Object]

结论

使用递归和缓存的最后一个解决方案可能不是最好的,但它是对象的真实深度副本。它处理简单的propertiescircular structuresnested object,但在克隆时会弄乱它们的实例。

jsfiddle

【讨论】:

所以结论是避免这个问题:) @mikus 直到有一个 真正的 规范,它涵盖的不仅仅是基本用例,是的。 对上面提供的解决方案进行了很好的分析,但作者得出的结论表明这个问题没有解决方案。 遗憾的是 JS 不包含原生克隆功能。 在所有最热门的答案中,我觉得这是接近正确的答案。【参考方案5】:

对 JavaScript 中的任何对象执行此操作都不会简单或直接。您将遇到错误地从对象原型中获取属性的问题,这些属性应该留在原型中而不是复制到新实例中。例如,如果您要向Object.prototype 添加clone 方法,正如某些答案所描述的那样,您将需要显式跳过该属性。但是,如果在Object.prototype 或其他中间原型中添加了您不知道的其他附加方法怎么办?在这种情况下,您将复制不应该复制的属性,因此您需要使用 hasOwnProperty 方法检测不可预见的非本地属性。

除了不可枚举的属性之外,当您尝试复制具有隐藏属性的对象时,您还会遇到更棘手的问题。例如,prototype 是函数的隐藏属性。此外,对象的原型由属性__proto__ 引用,该属性也是隐藏的,并且不会被迭代源对象属性的for/in 循环复制。我认为__proto__ 可能特定于 Firefox 的 JavaScript 解释器,在其他浏览器中可能有所不同,但你明白了。并非所有事物都是可枚举的。如果您知道它的名称,您可以复制隐藏的属性,但我不知道有什么方法可以自动发现它。

寻求优雅解决方案的另一个障碍是正确设置原型继承的问题。如果您的源对象的原型是Object,那么只需使用 创建一个新的通用对象即可,但如果源的原型是Object 的某个后代,那么您将丢失该原型中的其他成员您使用hasOwnProperty 过滤器跳过了哪些,或者哪些在原型中,但一开始就无法枚举。一种解决方案可能是调用源对象的constructor 属性来获取初始复制对象,然后复制属性,但是您仍然不会获得不可枚举的属性。例如,Date 对象将其数据存储为隐藏成员:

function clone(obj) 
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) 
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    
    return copy;


var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function()
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
, 5000);

d1 的日期字符串将比 d2 的日期字符串晚 5 秒。一种使Date 与另一个相同的方法是调用setTime 方法,但这是特定于Date 类的。我认为这个问题没有万无一失的通用解决方案,尽管我很乐意犯错!

当我不得不实现一般的深度复制时,我最终妥协了,假设我只需要复制一个普通的 ObjectArrayDateStringNumberBoolean .最后 3 种类型是不可变的,所以我可以执行浅拷贝而不用担心它会改变。我进一步假设ObjectArray 中包含的任何元素也将是该列表中的6 个简单类型之一。这可以通过如下代码来完成:

function clone(obj) 
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) 
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    

    // Handle Array
    if (obj instanceof Array) 
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) 
            copy[i] = clone(obj[i]);
        
        return copy;
    

    // Handle Object
    if (obj instanceof Object) 
        copy = ;
        for (var attr in obj) 
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        
        return copy;
    

    throw new Error("Unable to copy obj! Its type isn't supported.");

只要对象和数组中的数据形成树形结构,上面的函数就可以很好地适用于我提到的 6 种简单类型。也就是说,对象中对相同数据的引用不超过一个。例如:

// This would be cloneable:
var tree = 
    "left"  :  "left" : null, "right" : null, "data" : 3 ,
    "right" : null,
    "data"  : 8
;

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = 
    "left"  :  "left" : null, "right" : null, "data" : 3 ,
    "data"  : 8
;
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = 
    "left"  :  "left" : null, "right" : null, "data" : 3 ,
    "data"  : 8
;
cyclicGraph["right"] = cyclicGraph;

它将无法处理任何 JavaScript 对象,但它可能足以用于许多目的,只要您不认为它只适用于您扔给它的任何东西。

【讨论】:

缺少符号键和符号值。现在,使用Object.getOwnPropertyDescriptors 更好。【参考方案6】:

我正在提供这个问题的答案,因为我在这里看不到任何可以解决 DOM 元素问题的本机递归实现。

问题在于&lt;element&gt; 具有parentchild 属性,它们链接到具有parentchild 值的其他元素,这些值指向原始&lt;element&gt;,导致无限递归或循环冗余

如果你的对象是安全简单的,比如


    '123':456

...那么这里的任何其他答案都可能有效。

但如果你有...


    '123':<reactJSComponent>,
    '456':document.createElement('div'),

...那么你需要这样的东西:

    // cloneVariable() : Clone variable, return null for elements or components.
var cloneVariable = function (args) 
    const variable = args.variable;

    if(variable === null) 
            return null;
    

    if(typeof(variable) === 'object') 
            if(variable instanceof htmlElement || variable.nodeType > 0) 
                    return null;
            

            if(Array.isArray(variable)) 
                    var arrayclone = [];

                    variable.forEach((element) => 
                            arrayclone.push(cloneVariable('variable':element));
                    );

                    return arrayclone;
            

            var objectclone = ;

            Object.keys(variable).forEach((field) => 
                    objectclone[field] = cloneVariable('variable':variable[field]);
            );

            return objectclone;
    

    return variable;

【讨论】:

【参考方案7】:

您可以在不修改父对象的情况下克隆您的对象 -

    /** [Object Extend]*/
    ( typeof Object.extend === 'function' ? undefined : ( Object.extend = function ( destination, source ) 
        for ( var property in source )
            destination[property] = source[property];
        return destination;
     ) );
    /** [/Object Extend]*/
    /** [Object clone]*/
    ( typeof Object.clone === 'function' ? undefined : ( Object.clone = function ( object ) 
        return this.extend( , object );
     ) );
    /** [/Object clone]*/

    let myObj = 
        a:1, b:2, c:3, d:
            a:1, b:2, c:3
        
    ;

    let clone = Object.clone( myObj );

    clone.a = 10;

    console.log('clone.a==>', clone.a); //==> 10

    console.log('myObj.a==>', myObj.a); //==> 1 // object not modified here

    let clone2 = Object.clone( clone );

    clone2.a = 20;

    console.log('clone2.a==>', clone2.a); //==> 20

    console.log('clone.a==>', clone.a); //==> 10 // object not modified here

【讨论】:

【参考方案8】:

每MDN:

如果你想要浅拷贝,使用Object.assign(, a) 对于“深”复制,使用JSON.parse(JSON.stringify(a))

不需要外部库但需要检查browser compatibility first。

【讨论】:

【参考方案9】:

如果您使用的是 TypeScript,需要支持旧版 Web 浏览器(因此不能使用 Object.assign),并且不使用内置克隆方法的库,您可以让自己成为 combine 助手在几行代码中。它结合了对象,如果你只有一个,就克隆它。

/** Creates a new object that combines the properties of the specified objects. */
function combine(...objs: []) 
    const combined = ;
    objs.forEach(o => Object.keys(o).forEach(p => combined[p] = o[p]));
    return combined;

【讨论】:

【参考方案10】:

如果你的对象是一个类(例如https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes):

var copiedObject = jQuery.extend(true, , originalObject);
copiedObject.__proto__ = originalObject.__proto__;

然后在copiedObject 中,您有一个originalObject 类及其所有方法的深度复制实例。

【讨论】:

【参考方案11】:

这是一个没有Object.assign() 缺陷的现代解决方案(不通过引用复制):

const cloneObj = (obj) => 
    return Object.keys(obj).reduce((dolly, key) => 
        dolly[key] = (obj[key].constructor === Object) ?
            cloneObj(obj[key]) :
            obj[key];
        return dolly;
    , );
;

【讨论】:

【参考方案12】:

我不知道这不适用于哪些情况,但它为我提供了一个数组的副本。我觉得它很可爱:) 希望它有帮助

copiedArr = origArr.filter(function(x)return true)

【讨论】:

【参考方案13】:
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

如果您想(浅)克隆一个 类实例 而不仅仅是一个属性对象,则使用 E​​S6 解决方案。

【讨论】:

这与let cloned = Object.assign(, obj) 有何不同? @ceztko 当obj 是类实例时,Object.assign() 不会克隆例如类方法(因为它们不可枚举)。【参考方案14】:

好的,我知道它有很多答案,但没有人指出,EcmaScript5 有 assign 方法,适用于 FF 和 Chrome,它复制可枚举和自己的属性和符号。

Object Assign

【讨论】:

不支持 IE :( 为什么需要支持一个已经不存在的浏览器?说真的,你不用担心,至少我们这里是这样做的,只是不支持和提醒用户,如果他使用的是任何 IE 版本。 我只是切换到这里提到的 JSON.parse 方法。但是,是的,IE 似乎很麻烦。我还必须将 find 和 findIndex 更改为 lodash 函数。 @Naomi 因为 IE 确实存在,不幸的是即使在 2017 年它仍然是一个足够流行的浏览器。也许不适合像我们这样精通技术的用户,但人们仍然使用它。考虑到 IE 11 甚至不支持它,这是一个很大的缺点!【参考方案15】:

在 ES-6 中,您可以简单地使用 Object.assign(...)。 例如:

let obj = person: 'Thor Odinson';
let clone = Object.assign(, obj);

这里有一个很好的参考: https://googlechrome.github.io/samples/object-assign-es6/

【讨论】:

它不会深度克隆对象。 这是一个任务,而不是副本。 clone.Title = "just a clone" 表示 obj.Title = "just a clone"。 @HoldOffHunger 你错了。在浏览器的 JS 控制台中检查 (let obj = person: 'Thor Odinson'; let clone = Object.assign(, obj); clone.title = "Whazzup";) @collapsar:这正是我检查的内容,然后 console.log(person) 将是“Whazzup”,而不是“Thor Odinson”。请参阅 August 的评论。 @HoldOffHunger 在 Chrome 60.0.3112.113 和 Edge 14.14393 中都不会发生; August 的评论不适用,因为 obj 属性的原始类型的值确实被克隆了。对象本身的属性值不会被克隆。【参考方案16】:

您可以简单地使用spread property 来复制没有引用的对象。但要小心(见 cmets),“副本”只是在最低的对象/数组级别。嵌套属性仍然是引用!


完整克隆:

let x = a: 'value1'
let x2 = ...x

// => mutate without references:

x2.a = 'value2'
console.log(x.a)    // => 'value1'

使用二级引用克隆:

const y = a: b: 'value3'
const y2 = ...y

// => nested object is still a references:

y2.a.b = 'value4'
console.log(y.a.b)    // => 'value4'

JavaScript 实际上本身并不支持深度克隆。使用效用函数。例如拉姆达:

http://ramdajs.com/docs/#clone

【讨论】:

这不起作用...当 x 将是一个数组时,它可能会起作用,例如 x= [ 'ab','cd',...] 这可行,但请记住这是一个 SHALLOW 副本,因此对其他对象的任何深度引用仍然是引用! 部分克隆也可以通过这种方式发生:const first = a: 'foo', b: 'bar'; const second = ...a = first【参考方案17】:

好的,这可能是浅拷贝的最佳选择。 if 遵循了许多使用 assign 的示例,但它也保留了继承和原型。它也很简单,适用于大多数类数组和对象,除了那些具有构造函数要求或只读属性的对象。但这意味着它对于 TypedArrays、RegExp、Date、Maps、Sets 和 Object 版本的原语(布尔值、字符串等)会惨遭失败。

function copy ( a )  return Object.assign( new a.constructor, a ) 

a 可以是任何 Object 或 class 构造的实例,但对于使用专门的 getter 和 setter 或具有构造函数要求的东西也不可靠,但对于更简单的情况,它会摇摆不定。它也适用于论点。

您也可以将其应用于基元以获得奇怪的结果,但是……除非它最终成为一个有用的 hack,谁在乎。

基本内置对象和数组的结果...

> a =  a: 'A', b: 'B', c: 'C', d: 'D' 
 a: 'A', b: 'B', c: 'C', d: 'D' 
> b = copy( a )
 a: 'A', b: 'B', c: 'C', d: 'D' 
> a = [1,2,3,4]
[ 1, 2, 3, 4 ]
> b = copy( a )
[ 1, 2, 3, 4 ]

并且由于平均 get/setter、构造函数所需的参数或只读属性以及对父亲的犯罪而失败。

> a = /\w+/g
/\w+/g
> b = copy( a )  // fails because source and flags are read-only
/(?:)/
> a = new Date ( '1/1/2001' )
2000-12-31T16:00:00.000Z
> b = copy( a )  // fails because Date using methods to get and set things
2017-02-04T14:44:13.990Z
> a = new Boolean( true )
[Boolean: true]
> b = copy( a )  // fails because of of sins against the father
[Boolean: false]
> a = new Number( 37 )
[Number: 37]
> b = copy( a )  // fails because of of sins against the father
[Number: 0]
> a = new String( 'four score and seven years ago our four fathers' )
[String: 'four score and seven years ago our four fathers']
> b = copy( a )  // fails because of of sins against the father
 [String: ''] '0': 'f', '1': 'o', '2': 'u', '3': 'r', '4': ' ', '5': 's', '6': 'c', '7': 'o', '8': 'r', '9': 'e', '10': ' ', '11': 'a', '12': 'n', '13': 'd', '14': ' ', '15': 's', '16': 'e', '17': 'v', '18': 'e', '19': 'n', '20': ' ', '21': 'y', '22': 'e', '23': 'a', '24': 'r', '25': 's', '26': ' ', '27': 'a', '28': 'g', '29': 'o', '30': ' ', '31': 'o', '32': 'u', '33': 'r', '34': ' ', '35': 'f', '36': 'o', '37': 'u', '38': 'r', '39': ' ', '40': 'f', '41': 'a', '42': 't', '43': 'h', '44': 'e', '45': 'r', '46': 's'  

【讨论】:

【参考方案18】:

老问题的新答案!如果您有幸将 ECMAScript 2016 (ES6) 与 Spread Syntax 一起使用,这很容易。

keepMeTheSame = first: "Me!", second: "You!";
cloned = ...keepMeTheSame

这为对象的浅拷贝提供了一种干净的方法。制作深层副本,即为每个递归嵌套对象中的每个值创建一个新副本,需要上述较重的解决方案。

JavaScript 不断发展。

【讨论】:

当你在对象上定义了函数时它不起作用 据我所知,传播运算符仅适用于迭代 - developer.mozilla.org 说:var obj = 'key1': 'value1'; var array = [...obj]; // TypeError: obj is not iterable @Oleh 所以使用 ` ... obj 而不是 [...obj];` @manikantgautam 我之前使用过 Object.assign(),但现在最新的 Chrome、Firefox(仍然不支持 Edge 和 Safari)确实支持对象传播语法。它的 ECMAScript 提案......但据我所知,Babel 确实支持它,所以它可能可以安全使用。【参考方案19】:

一个特别不优雅的解决方案是使用 JSON 编码来制作没有成员方法的对象的深层副本。该方法是对您的目标对象进行 JSON 编码,然后通过对其进行解码,您可以获得所需的副本。您可以根据需要进行多次解码,制作尽可能多的副本。

当然,函数不属于 JSON,所以这只适用于没有成员方法的对象。

这种方法非常适合我的用例,因为我将 JSON blob 存储在键值存储中,并且当它们在 JavaScript API 中作为对象公开时,每个对象实际上都包含原始状态的副本对象,这样我们就可以在调用者改变暴露的对象后计算增量。

var object1 = key:"value";
var object2 = object1;

object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);

object2.key = "a change";
console.log(object1);// returns value

【讨论】:

为什么函数不属于JSON?我不止一次看到它们以 JSON 格式传输... 函数不是 JSON 规范的一部分,因为它们不是一种安全(或智能)的数据传输方式,而这正是 JSON 的用途。我知道 Firefox 中的原生 JSON 编码器只是忽略传递给它的函数,但我不确定其他人的行为。 @mark: 'foo': function() return 1; 是文字构造的对象。 @abarnert 函数不是数据。 “函数字面量”用词不当——因为函数可以包含任意代码,包括赋值和各种“不可序列化”的东西。【参考方案20】:

我认为,缓存重复是我们在没有库的情况下可以做到的最好的方法。

而被低估的 WeakMap 会遇到循环问题,其中存储对新旧对象的引用对可以帮助我们轻松地重新创建整棵树。

我阻止了 DOM 元素的深度克隆,您可能不想克隆整个页面:)

function deepCopy(object) 
    const cache = new WeakMap(); // Map of old - new references

    function copy(obj) 
        if (typeof obj !== 'object' ||
            obj === null ||
            obj instanceof HTMLElement
        )
            return obj; // primitive value or HTMLElement

        if (obj instanceof Date) 
            return new Date().setTime(obj.getTime());

        if (obj instanceof RegExp) 
            return new RegExp(obj.source, obj.flags);

        if (cache.has(obj)) 
            return cache.get(obj);

        const result = obj instanceof Array ? [] : ;

        cache.set(obj, result); // store reference to object before the recursive starts

        if (obj instanceof Array) 
            for(const o of obj) 
                 result.push(copy(o));
            
            return result;
        

        const keys = Object.keys(obj); 

        for (const key of keys)
            result[key] = copy(obj[key]);

        return result;
    

    return copy(object);

一些测试:

// #1
const obj1 =  ;
const obj2 =  ;
obj1.obj2 = obj2;
obj2.obj1 = obj1; // Trivial circular reference

var copy = deepCopy(obj1);
copy == obj1 // false
copy.obj2 === obj1.obj2 // false
copy.obj2.obj1.obj2 // and so on - no error (correctly cloned).

// #2
const obj =  x: 0 
const clone = deepCopy( a: obj, b: obj );
clone.a == clone.b // true

// #3
const arr = [];
arr[0] = arr; // A little bit weird but who cares
clone = deepCopy(arr)
clone == arr // false;
clone[0][0][0][0] == clone // true;

注意:我使用常量、for of 循环、=> 运算符和 WeakMaps 来创建更重要的代码。今天的浏览器支持这种语法(ES6)

【讨论】:

【参考方案21】:

我已经编写了自己的实现。不确定它是否算作更好的解决方案:

/*
    a function for deep cloning objects that contains other nested objects and circular structures.
    objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.
                                    index (z)
                                         |
                                         |
                                         |
                                         |
                                         |
                                         |                      depth (x)
                                         |_ _ _ _ _ _ _ _ _ _ _ _
                                        /_/_/_/_/_/_/_/_/_/
                                       /_/_/_/_/_/_/_/_/_/
                                      /_/_/_/_/_/_/...../
                                     /................./
                                    /.....            /
                                   /                 /
                                  /------------------
            object length (y)    /
*/

以下是实现:

function deepClone(obj) 
    var depth = -1;
    var arr = [];
    return clone(obj, arr, depth);


/**
 *
 * @param obj source object
 * @param arr 3D array to store the references to objects
 * @param depth depth of the current object relative to the passed 'obj'
 * @returns *
 */
function clone(obj, arr, depth)
    if (typeof obj !== "object") 
        return obj;
    

    var length = Object.keys(obj).length; // native method to get the number of properties in 'obj'

    var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original object
    if(result instanceof Array)
        result.length = length;
    

    depth++; // depth is increased because we entered an object here

    arr[depth] = []; // this is the x-axis, each index here is the depth
    arr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props)
    // start the depth at current and go down, cyclic structures won't form on depths more than the current one
    for(var x = depth; x >= 0; x--)
        // loop only if the array at this depth and length already have elements
        if(arr[x][length])
            for(var index = 0; index < arr[x][length].length; index++)
                if(obj === arr[x][length][index])
                    return obj;
                
            
        
    

    arr[depth][length].push(obj); // store the object in the array at the current depth and length
    for (var prop in obj) 
        if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth);
    

    return result;

【讨论】:

不适用于我的对象,尽管我的情况有点复杂。【参考方案22】:

要处理 JSON.stringify 无法处理的循环对象,您可以引入一个名为 JSOG 的库,它将任意图形序列化和反序列化为 JSON 格式。

var clone = JSOG.parse(JSOG.stringify(original));

尝试用这个技巧修补 JSOG 以进行克隆也可能很有趣(目前没有时间,但如果有人想试一试......):

序列化一个简单的函数:

foo.f = function(a)  return a 
var stringForm = foo.f.toString() // "function (a)  return a "

反序列化函数:

eval("foo.f = " + stringForm)

需要一些约定(可能以属性的名称)来识别函数与常规字符串(可能是@func_f)。

当然,如果函数调用第二个函数,则第二个函数将需要像原来的函数一样存在。

但是,如果您要接受来自不受信任来源的序列化表单,则上述内容非常危险,但是接受来自不受信任来源的任何形式的任何函数都是危险的,因此,如果您对克隆函数感兴趣,则信任必须具有已经建立(或者您已经打算编写安全漏洞!)。

免责声明:我没有测试过 JSOG stringify/parse vs JSON stringify/parse 的速度,但它确实适用于我测试过的简单(圆形)对象。

【讨论】:

【参考方案23】:

在一行代码中克隆 Javascript 对象的优雅方法

Object.assign 方法是 ECMAScript 2015 (ES6) 标准的一部分,可以满足您的需求。

var clone = Object.assign(, obj);

Object.assign() 方法用于将所有可枚举自身属性的值从一个或多个源对象复制到目标对象。

Read more...

polyfill 支持旧版浏览器:

if (!Object.assign) 
  Object.defineProperty(Object, 'assign', 
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) 
      'use strict';
      if (target === undefined || target === null) 
        throw new TypeError('Cannot convert first argument to object');
      

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) 
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) 
          continue;
        
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) 
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) 
            to[nextKey] = nextSource[nextKey];
          
        
      
      return to;
    
  );

【讨论】:

这只会执行浅“克隆”【参考方案24】:

如果你对浅拷贝没问题,underscore.js 库有一个clone 方法。

y = _.clone(x);

或者你可以像这样扩展它

copiedObject = _.extend(,originalObject);

【讨论】:

谢谢。在 Meteor 服务器上使用这种技术。 要快速开始使用 lodash,我建议学习 npm、Browserify 以及 lodash。我让克隆与 'npm i --save lodash.clone' 一起工作,然后是 'var clone = require('lodash.clone');'要让 require 工作,你需要类似 browserify 的东西。安装并了解它的工作原理后,每次运行代码时都将使用“browserify yourfile.js > bundle.js;start chrome index.html”(而不是直接进入 Chrome)。这会将您的文件和 npm 模块所需的所有文件收集到 bundle.js 中。不过,您可能可以使用 Gulp 节省时间并自动执行此步骤。【参考方案25】:

我已经在标量对象的情况下尝试过这个,它对我有用:

function binder(i) 
  return function () 
    return i;
  ;


a=1;
b=binder(a)(); // copy value of a into b

alert(++a); // 2
alert(b); // still 1

问候。

【讨论】:

a=1;b=a;alert(++a);alert(b); // still 1【参考方案26】:

对于那些使用 AngularJS 的人,也有直接的方法来克隆或扩展这个库中的对象。

var destination = angular.copy(source);

angular.copy(source, destination);

更多内容见 angular.copy documentation...

【讨论】:

这是一个深拷贝仅供参考。【参考方案27】:

复制一个最终可能指向自身的对象的问题可以通过简单的检查来解决。添加此检查,每次有复制操作。它可能,但它应该工作。

我使用 toType() 函数显式地返回对象类型。我也有自己的 copyObj() 函数,它在逻辑上非常相似,它回答了所有三种 Object()、Array() 和 Date() 情况。

我在 NodeJS 中运行它。

尚未测试。

// Returns true, if one of the parent's children is the target.
// This is useful, for avoiding copyObj() through an infinite loop!
function isChild(target, parent) 
  if (toType(parent) == '[object Object]') 
    for (var name in parent) 
      var curProperty = parent[name];

      // Direct child.
      if (curProperty = target) return true;

      // Check if target is a child of this property, and so on, recursively.
      if (toType(curProperty) == '[object Object]' || toType(curProperty) == '[object Array]') 
        if (isChild(target, curProperty)) return true;
      
    
   else if (toType(parent) == '[object Array]') 
    for (var i=0; i < parent.length; i++) 
      var curItem = parent[i];

      // Direct child.
      if (curItem = target) return true;

      // Check if target is a child of this property, and so on, recursively.
      if (toType(curItem) == '[object Object]' || toType(curItem) == '[object Array]') 
        if (isChild(target, curItem)) return true;
      
    
  

  return false;     // Not the target.

【讨论】:

【参考方案28】:

您可以使用函数闭包来获得深拷贝的所有好处,而无需深拷贝。这是一个非常不同的范例,但效果很好。与其尝试复制现有对象,不如在需要时使用一个函数来实例化一个新对象。

首先,创建一个返回对象的函数

function template() 
  return 
    values: [1, 2, 3],
    nest: x: a: "a", b: "b", y: 100
  ;

然后创建一个简单的浅拷贝函数

function copy(a, b) 
  Object.keys(b).forEach(function(key) 
    a[key] = b[key];
  );

创建一个新对象,并将模板的属性复制到它上面

var newObject = ; 
copy(newObject, template());

但是上面的复制步骤不是必须的。您需要做的就是:

var newObject = template();

现在你有了一个新对象,测试一下它的属性是什么:

console.log(Object.keys(newObject));

这显示:

["values", "nest"]

是的,这些是 newObject 自己的属性,而不是对另一个对象的属性的引用。 让我们检查一下:

console.log(newObject.nest.x.b);

这显示:

"b"

newObject 已获取模板对象的所有属性,但没有任何依赖链。

http://jsbin.com/ISUTIpoC/1/edit?js,console

我添加了这个例子来鼓励一些辩论,所以请添加一些 cmets :)

【讨论】:

Object.keys 直到 JavaScript 1.8.5 才实现,这意味着它在 IE 8 和其他旧版浏览器中不可用。因此,虽然这个答案在现代浏览器中效果很好,但在 IE 8 中会失败。所以如果你使用这种方法,你必须使用正确模拟 Object.keys polyfill。【参考方案29】:
//
// creates 'clone' method on context object
//
//  var 
//     clon = Object.clone( anyValue );
//
!((function (propertyName, definition) 
    this[propertyName] = definition();
).call(
    Object,
    "clone",
    function () 
        function isfn(fn) 
            return typeof fn === "function";
        

        function isobj(o) 
            return o === Object(o);
        

        function isarray(o) 
            return Object.prototype.toString.call(o) === "[object Array]";
        

        function fnclon(fn) 
            return function () 
                fn.apply(this, arguments);
            ;
        

        function owns(obj, p) 
            return obj.hasOwnProperty(p);
        

        function isemptyobj(obj) 
            for (var p in obj) 
                return false;
            
            return true;
        

        function isObject(o) 
            return Object.prototype.toString.call(o) === "[object Object]";
        
        return function (input) 
            if (isfn(input)) 
                return fnclon(input);
             else if (isobj(input)) 
                var cloned = ;
                for (var p in input) 
                    owns(Object.prototype, p)
                    || (
                        isfn(input[p])
                        && ( cloned[p] = function ()  return input[p].apply(input, arguments);  )
                        || ( cloned[p] = input[p] )
                    );
                
                if (isarray(input)) 
                    cloned.length = input.length;
                    "concat every filter forEach indexOf join lastIndexOf map pop push reduce reduceRight reverse shift slice some sort splice toLocaleString toString unshift"
                    .split(" ")
                    .forEach(
                      function (methodName) 
                        isfn( Array.prototype[methodName] )
                        && (
                            cloned[methodName] =
                            function () 
                                return Array.prototype[methodName].apply(cloned, arguments);
                            
                        );
                      
                    );
                
                return isemptyobj(cloned)
                       ? (
                          isObject(input)
                          ? cloned
                          : input
                        )
                       : cloned;
             else 
                return input;
            
        ;
    
));
//

【讨论】:

为什么这个答案比其他任何一个都好? 你是从任何框架或库中得到这个的,还是你创建的?我不知道,它对我来说似乎不可读。【参考方案30】:

如果你有一个带有函数的对象,你可以用 JSONfn 来做,见http://www.eslinstructor.net/jsonfn/。

var obj= 
    name:'Marvin',
    getName :  function()
      return this.name;
    

var cobj = JSONfn.parse(JSONfn.stringify(obj));

【讨论】:

以上是关于如何正确克隆 JavaScript 对象?的主要内容,如果未能解决你的问题,请参考以下文章

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

关于JavaScript对象深度克隆