如何正确克隆JavaScript对象?

2020/09/14 01:31 · javascript ·  · 0评论

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

如何正确克隆JavaScript对象?

在JavaScript中对任何对象执行此操作都不是简单或直接的。您将遇到错误地从对象的原型中选取应保留在原型中而不应复制到新实例的属性的问题。例如,如果您正在向中添加clone方法Object.prototype,如一些答案所示,则需要显式跳过该属性。但是,如果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将比的日期字符串晚5秒d2使一个Date与另一个相同的setTime方法是调用方法,但这特定于Date该类。尽管我很乐意弄错,但我认为没有解决该问题的通用解决方案!

当我不得不实施一般深拷贝我最终通过假设我只需要复制一个普通的妥协ObjectArrayDateStringNumber,或Boolean后三种类型是不可变的,因此我可以执行浅表复制,而不必担心它会更改。我进一步假定包含在其中的任何元素,Object或者Array也将是该列表中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对象,但是只要您不认为它会对您扔给它的任何东西起作用,它就可以满足许多目的。

如果您Date在对象内未使用s,function,undefined,regExp或Infinity,则非常简单的一种衬里是JSON.parse(JSON.stringify(object))

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

这适用于包含对象,数组,字符串,布尔值和数字的所有类型的对象。

另请参阅本文有关浏览器的结构化克隆算法的文章,该算法在与工作人员之间发布消息时使用。它还包含用于深度克隆的功能。

使用jQuery,您可以使用extend进行浅表复制

var copiedObject = jQuery.extend({}, originalObject)

对的后续更改copiedObject不会影响originalObject,反之亦然。

或进行深复制

var copiedObject = jQuery.extend(true, {}, originalObject)

在ECMAScript 6中,存在Object.assign方法,该方法将所有可枚举的自身属性的值从一个对象复制到另一个对象。例如:

var x = {myProp: "value"};
var y = Object.assign({}, x); 

但是请注意,嵌套对象仍会复制为引用。

每个MDN

  • 如果要浅拷贝,请使用 Object.assign({}, a)
  • 对于“深层”副本,请使用 JSON.parse(JSON.stringify(a))

不需要外部库,但您需要首先检查浏览器的兼容性

有很多答案,但是没有一个提到ECMAScript 5 中的Object.create,它当然不能提供确切的副本,但是会将源设置为新对象的原型。

因此,这不是对该问题的确切答案,而是单线解决方案,因此很优雅。它最适合2种情况:

  1. 此类继承有用的地方(du!)
  2. 不会修改源对象的地方,因此这两个对象之间的关系就不成问题了。

例:

var foo = { a : 1 };
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property

为什么我认为此解决方案更好?它是本机的,因此没有循环,没有递归。但是,较旧的浏览器将需要使用polyfill。

An elegant way to clone a Javascript object in one line of code

An Object.assign method is part of the ECMAScript 2015 (ES6) standard and does exactly what you need.

var clone = Object.assign({}, obj);

The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object.

Read more...

The polyfill to support older browsers:

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;
    }
  });
}

互联网上大多数解决方案都存在一些问题。因此,我决定进行跟进,包括为什么不接受接受的答案。

起始情况

我想复制一个Javascript Object及其所有子级及其子级,依此类推。但是由于我不是一个普通的开发人员,所以我Object拥有正常的 propertiescircular structures甚至nested objects

因此,让我们创建一个circular structurenested object第一个。

function Circ() {
    this.me = this;
}

function Nested(y) {
    this.y = y;
}

让我们将所有内容放在一起Object命名为a

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甚至是handles的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像IE 8一样,较旧的浏览器中也有一个polyfill 。这有点像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

与本机解决方案相同的问题,但输出会差一些。

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

进行深入研究时,我发现了与此类似的问题(在Javascript中,当执行深度复制时,由于属性为“ 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

要求相匹配,但仍有一些小问题,包括不断变化instancenestedcircObject

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

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

结论

最后一种使用递归和缓存的解决方案可能不是最好的,但它是对象的真正的深层复制。它操作简单propertiescircular structures并且nested object,却会同时克隆弄乱它们的实例。

jsfiddle

如果您可以使用浅表副本,那么underscore.js库提供了一个clone方法。

y = _.clone(x);

或者你可以像扩展它

copiedObject = _.extend({},originalObject);

好的,假设您在下面有这个对象,并且想要克隆它:

let obj = {a:1, b:2, c:3}; //ES6

要么

var obj = {a:1, b:2, c:3}; //ES5

答案主要取决于您使用哪种ECMAscript,在ES6+中您可以简单地使用它Object.assign来进行克隆:

let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};

或使用像这样的传播算子:

let cloned = {...obj}; //new {a:1, b:2, c:3};

但是,如果您使用ES5,则可以使用几种方法,但是使用JSON.stringify,只需确保不使用要复制的大量数据即可,但是在许多情况下,这可能是一种方便的方式,如下所示:

let cloned = JSON.parse(JSON.stringify(obj)); 
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over

一种特别优雅的解决方案是使用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

您可以简单地使用spread属性复制没有引用的对象。但是要小心(请参阅注释),“副本”仅位于最低的对象/数组级别。嵌套属性仍然是参考!


完整克隆:

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本身不支持深度克隆。使用实用程序功能。例如Ramda:

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

对于使用AngularJS的用户,此库中还有直接方法来克隆或扩展对象。

var destination = angular.copy(source);

要么

angular.copy(source, destination);

更多angular.copy 文档 ...

答:Levy的答案几乎是完整的,这是我的一点贡献:有一种方法可以处理递归引用,请参见此行

if(this[attr]==this) copy[attr] = copy;

如果对象是XML DOM元素,我们必须使用cloneNode代替

if(this.cloneNode) return this.cloneNode(true);

受A.Levy详尽研究和Calvin原型制作方法的启发,我提供了以下解决方案:

Object.prototype.clone = function() {
  if(this.cloneNode) return this.cloneNode(true);
  var copy = this instanceof Array ? [] : {};
  for(var attr in this) {
    if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone)
      copy[attr] = this[attr];
    else if(this[attr]==this) copy[attr] = copy;
    else copy[attr] = this[attr].clone();
  }
  return copy;
}

Date.prototype.clone = function() {
  var copy = new Date();
  copy.setTime(this.getTime());
  return copy;
}

Number.prototype.clone = 
Boolean.prototype.clone =
String.prototype.clone = function() {
  return this;
}

另请参阅答案中的Andy Burke的注释。

摘自本文:如何用Brian Huisman的Javascript复制数组和对象

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (var i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
};

这是您可以使用的功能。

function clone(obj) {
    if(obj == null || typeof(obj) != 'object')
        return obj;    
    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);    
    return temp;
}

在ECMAScript 2018中

let objClone = { ...obj };

请注意,嵌套对象仍将被复制为参考。

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

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

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

使用Lodash:

var y = _.clone(x, true);

对克隆简单对象感兴趣:

JSON.parse(JSON.stringify(json_original));

源:如何不通过引用将JavaScript对象复制到新变量?

您可以使用一个单行代码克隆对象并从上一个引用中删除任何引用。只需做:

var obj1 = { text: 'moo1' };
var obj2 = Object.create(obj1); // Creates a new clone without references

obj2.text = 'moo2'; // Only updates obj2's text property

console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}

对于当前不支持Object.create的浏览器/引擎,可以使用此polyfill:

// Polyfill Object.create if it does not exist
if (!Object.create) {
    Object.create = function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
    };
}

旧问题的新答案!如果您高兴地将ECMAScript 2016(ES6)与Spread Syntax一起使用,这很简单。

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

这为对象的浅表副本提供了一种干净的方法。进行深层复制(意味着要递归地嵌套每个对象中的每个值)都需要上面较重的解决方案。

JavaScript不断发展。

let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

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

我认为有一个简单可行的答案。在深度复制中,有两个问题:

  1. 使属性彼此独立。
  2. 并使方法在克隆对象上保持活动状态。

因此,我认为一个简单的解决方案是先进行序列化和反序列化,然后再对其进行赋值以复制函数。

let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);

尽管这个问题有很多答案,但我希望这个问题也能有所帮助。

对于深层复制和克隆,先JSON.stringify然后再JSON.parse对象:

obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}

2020年7月6日更新

有三(3)种方法可以在JavaScript中克隆对象。由于JavaScript中的对象是参考值,因此不能仅使用=复制。

方法是:

const food = { food: 'apple', drink: 'milk' }


// 1. Using the "Spread"
// ------------------

{ ...food }


// 2. Using "Object.assign"
// ------------------

Object.assign({}, food)


// 3. "JSON"
// ------------------

JSON.parse(JSON.stringify(food))

// RESULT:
// { food: 'apple', drink: 'milk' }

希望可以将其用作参考摘要。

这是对A. Levy的代码的改编,它也可以处理函数和多个/循环引用的克隆-这意味着,如果要克隆的树中的两个属性是同一对象的引用,则克隆的对象树将具有这些属性指向引用对象的一个​​克隆。这也解决了循环依赖的情况,如果不加以处理,则会导致无限循环。该算法的复杂度为O(n)

function clone(obj){
    var clonedObjectsArray = [];
    var originalObjectsArray = []; //used to remove the unique ids when finished
    var next_objid = 0;

    function objectId(obj) {
        if (obj == null) return null;
        if (obj.__obj_id == undefined){
            obj.__obj_id = next_objid++;
            originalObjectsArray[obj.__obj_id] = obj;
        }
        return obj.__obj_id;
    }

    function cloneRecursive(obj) {
        if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") 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; i < obj.length; ++i) {
                copy[i] = cloneRecursive(obj[i]);
            }
            return copy;
        }

        // Handle Object
        if (obj instanceof Object) {
            if (clonedObjectsArray[objectId(obj)] != undefined)
                return clonedObjectsArray[objectId(obj)];

            var copy;
            if (obj instanceof Function)//Handle Function
                copy = function(){return obj.apply(this, arguments);};
            else
                copy = {};

            clonedObjectsArray[objectId(obj)] = copy;

            for (var attr in obj)
                if (attr != "__obj_id" && obj.hasOwnProperty(attr))
                    copy[attr] = cloneRecursive(obj[attr]);                 

            return copy;
        }       


        throw new Error("Unable to copy obj! Its type isn't supported.");
    }
    var cloneObj = cloneRecursive(obj);



    //remove the unique ids
    for (var i = 0; i < originalObjectsArray.length; i++)
    {
        delete originalObjectsArray[i].__obj_id;
    };

    return cloneObj;
}

一些快速测试

var auxobj = {
    prop1 : "prop1 aux val", 
    prop2 : ["prop2 item1", "prop2 item2"]
    };

var obj = new Object();
obj.prop1 = "prop1_value";
obj.prop2 = [auxobj, auxobj, "some extra val", undefined];
obj.nr = 3465;
obj.bool = true;

obj.f1 = function (){
    this.prop1 = "prop1 val changed by f1";
};

objclone = clone(obj);

//some tests i've made
console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));

objclone.f1();
console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));
objclone.f1.prop = 'some prop';
console.log("test function cloning 2: " + (obj.f1.prop == undefined));

objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));

使用lodash _.cloneDeep()。

浅表复制:lodash _.clone()

可以通过简单地复制参考来进行浅表复制。

let obj1 = {
    a: 0,
    b: {
        c: 0,
        e: {
            f: 0
        }
    }
};
let obj3 = _.clone(obj1);
obj1.a = 4;
obj1.b.c = 4;
obj1.b.e.f = 100;

console.log(JSON.stringify(obj1));
//{"a":4,"b":{"c":4,"e":{"f":100}}}

console.log(JSON.stringify(obj3));
//{"a":0,"b":{"c":4,"e":{"f":100}}}

浅表复制:lodash _.clone()

深度复制:lodash _.cloneDeep()

字段被取消引用:而不是引用要复制的对象

let obj1 = {
    a: 0,
    b: {
        c: 0,
        e: {
            f: 0
        }
    }
};
let obj3 = _.cloneDeep(obj1);
obj1.a = 100;
obj1.b.c = 100;
obj1.b.e.f = 100;

console.log(JSON.stringify(obj1));
{"a":100,"b":{"c":100,"e":{"f":100}}}

console.log(JSON.stringify(obj3));
{"a":0,"b":{"c":0,"e":{"f":0}}}

深度复制:lodash _.cloneDeep()

(以下是主要@的整合马切伊·布考斯基,@ A.征,@ 扬Turoň,@ 热度的答案,@ LeviRoberts,@ RobG的意见,非常感谢他们!)

深复制—是的!(大多);
浅拷贝—不!(除外Proxy)。

我真诚欢迎大家参加测试clone()

另外,
defineProp()旨在轻松,快速地(重新)定义或复制任何类型的描述符。

功能

"use strict"
function clone(object) {
  /*
    Deep copy objects by value rather than by reference,
    exception: `Proxy`
  */

  const seen = new WeakMap()

  return clone(object)


  function clone(object) {
    if (object !== Object(object)) return object /*
    —— Check if the object belongs to a primitive data type */

    if (object instanceof Node) return object.cloneNode(true) /*
    —— Clone DOM trees */

    let _object // The clone of object

    switch (object.constructor) {
      case Array:
      case Object:
        _object = cloneObject(object)
        break

      case Date:
        _object = new Date(+object)
        break

      case Function:
        const fnStr = String(object)

        _object = new Function("return " +
          (/^(?!function |[^{]+?=>)[^(]+?\(/.test(fnStr)
            ? "function " : ""
          ) + fnStr
        )()

        copyPropDescs(_object, object)
        break

      case RegExp:
        _object = new RegExp(object)
        break

      default:
        switch (Object.prototype.toString.call(object.constructor)) {
          //                              // Stem from:
          case "[object Function]":       // `class`
          case "[object Undefined]":      // `Object.create(null)`
            _object = cloneObject(object)
            break

          default:                        // `Proxy`
            _object = object
        }
    }

    return _object
  }


  function cloneObject(object) {
    if (seen.has(object)) return seen.get(object) /*
    —— Handle recursive references (circular structures) */

    const _object = Array.isArray(object)
      ? []
      : Object.create(Object.getPrototypeOf(object)) /*
        —— Assign [[Prototype]] for inheritance */

    seen.set(object, _object) /*
    —— Make `_object` the associative mirror of `object` */

    Reflect.ownKeys(object).forEach(key =>
      defineProp(_object, key, { value: clone(object[key]) }, object)
    )

    return _object
  }


  function copyPropDescs(target, source) {
    Object.defineProperties(target,
      Object.getOwnPropertyDescriptors(source)
    )
  }
}


function defineProp(object, key, descriptor = {}, copyFrom = {}) {
  const { configurable: _configurable, writable: _writable }
    = Object.getOwnPropertyDescriptor(object, key)
    || { configurable: true, writable: true }

  const test = _configurable // Can redefine property
    && (_writable === undefined || _writable) // Can assign to property

  if (!test || arguments.length <= 2) return test

  const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
    || { configurable: true, writable: true } // Custom…
    || {}; // …or left to native default settings

  ["get", "set", "value", "writable", "enumerable", "configurable"]
    .forEach(attr =>
      descriptor[attr] === undefined &&
      (descriptor[attr] = basisDesc[attr])
    )

  const { get, set, value, writable, enumerable, configurable }
    = descriptor

  return Object.defineProperty(object, key, {
    enumerable, configurable, ...get || set
      ? { get, set } // Accessor descriptor
      : { value, writable } // Data descriptor
  })
}

//测试

"use strict"
const obj0 = {
  u: undefined,
  nul: null,
  t: true,
  num: 9,
  str: "",
  sym: Symbol("symbol"),
  [Symbol("e")]: Math.E,
  arr: [[0], [1, 2]],
  d: new Date(),
  re: /f/g,
  get g() { return 0 },
  o: {
    n: 0,
    o: { f: function (...args) { } }
  },
  f: {
    getAccessorStr(object) {
      return []
        .concat(...
          Object.values(Object.getOwnPropertyDescriptors(object))
            .filter(desc => desc.writable === undefined)
            .map(desc => Object.values(desc))
        )
        .filter(prop => typeof prop === "function")
        .map(String)
    },
    f0: function f0() { },
    f1: function () { },
    f2: a => a / (a + 1),
    f3: () => 0,
    f4(params) { return param => param + params },
    f5: (a, b) => ({ c = 0 } = {}) => a + b + c
  }
}

defineProp(obj0, "s", { set(v) { this._s = v } })
defineProp(obj0.arr, "tint", { value: { is: "non-enumerable" } })
obj0.arr[0].name = "nested array"


let obj1 = clone(obj0)
obj1.o.n = 1
obj1.o.o.g = function g(a = 0, b = 0) { return a + b }
obj1.arr[1][1] = 3
obj1.d.setTime(+obj0.d + 60 * 1000)
obj1.arr.tint.is = "enumerable? no"
obj1.arr[0].name = "a nested arr"
defineProp(obj1, "s", { set(v) { this._s = v + 1 } })
defineProp(obj1.re, "multiline", { value: true })

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Routinely")

console.log("obj0:\n ", JSON.stringify(obj0))
console.log("obj1:\n ", JSON.stringify(obj1))
console.log()

console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)
console.log()

console.log("obj0\n ",
  ".arr.tint:", obj0.arr.tint, "\n ",
  ".arr[0].name:", obj0.arr[0].name
)
console.log("obj1\n ",
  ".arr.tint:", obj1.arr.tint, "\n ",
  ".arr[0].name:", obj1.arr[0].name
)
console.log()

console.log("Accessor-type descriptor\n ",
  "of obj0:", obj0.f.getAccessorStr(obj0), "\n ",
  "of obj1:", obj1.f.getAccessorStr(obj1), "\n ",
  "set (obj0 & obj1) .s :", obj0.s = obj1.s = 0, "\n ",
  "  → (obj0 , obj1) ._s:", obj0._s, ",", obj1._s
)

console.log("—— obj0 has not been interfered.")

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Circular structures")

obj0.o.r = {}
obj0.o.r.recursion = obj0.o
obj0.arr[1] = obj0.arr

obj1 = clone(obj0)
console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)

console.log("Clear obj0's recursion:",
  obj0.o.r.recursion = null, obj0.arr[1] = 1
)
console.log(
  "obj0\n ",
  ".o.r:", obj0.o.r, "\n ",
  ".arr:", obj0.arr
)
console.log(
  "obj1\n ",
  ".o.r:", obj1.o.r, "\n ",
  ".arr:", obj1.arr
)
console.log("—— obj1 has not been interfered.")


console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Classes")

class Person {
  constructor(name) {
    this.name = name
  }
}

class Boy extends Person { }
Boy.prototype.sex = "M"

const boy0 = new Boy
boy0.hobby = { sport: "spaceflight" }

const boy1 = clone(boy0)
boy1.hobby.sport = "superluminal flight"

boy0.name = "one"
boy1.name = "neo"

console.log("boy0:\n ", boy0)
console.log("boy1:\n ", boy1)
console.log("boy1's prototype === boy0's:",
  Object.getPrototypeOf(boy1) === Object.getPrototypeOf(boy0)
)

参考文献

  1. Object.create()| MDN
  2. Object.defineProperties()| MDN
  3. 财产的可枚举性和所有权| MDN
  4. TypeError:循环对象值| MDN

语言技巧

  1. 有条件地向对象添加道具

我只是想添加到本文中的所有Object.create解决方案中,以至于nodejs无法以所需的方式工作。

在Firefox中,结果

var a = {"test":"test"};
var b = Object.create(a);
console.log(b);´

{test:"test"}

在nodejs中

{}
本文地址:http://javascript.askforanswer.com/ruhezhengquekelongjavascriptduixiang.html
文章标签: ,   ,  
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!

文件下载

老薛主机终身7折优惠码boke112

上一篇:
下一篇:

评论已关闭!