Node.js:如何克隆对象


90

如果克隆数组,我将使用 cloneArr = arr.slice()

我想知道如何在nodejs中克隆对象。

Answers:


175

对于不需要压缩每一滴性能的实用程序和类,我经常作弊,只是使用JSON执行深层复制:

function clone(a) {
   return JSON.parse(JSON.stringify(a));
}

这不是唯一的答案,也不是最优雅的答案。所有其他答案都应考虑成为生产瓶颈。但是,这是一个快速而肮脏的解决方案,非常有效,并且在大多数情况下(我会克隆属性的简单哈希值)很有用。


2
@djechlin当然可以。尝试一下:jsfiddle.net/katowulf/E5jC3(已在节点0.10.11上进行了测试)它不能重新构造函数或原型数据,但可以很好地获得值。
加藤

11
这会将日期转换为字符串
backus

4
@Backus以及对象和功能。
加藤

2
@Kato,为什么循环引用意味着不需要进行深拷贝?他们是两件事。当然,可以为NodeJS编写一个很好的克隆方法,该方法支持循环引用并且不依赖于使用JSON进行克隆。github.com/pvorb/node-clone
塞缪尔·内夫

2
已经在注释和描述中指出。您不能克隆函数,对象原型或其他您不应该尝试使用通用克隆方法进行的操作。为此,您将需要多行代码,并且无论如何都不应该克隆该垃圾-在您的类上放置一个知道如何处理内部结构并执行操作的克隆方法newObj = obj.clone(args...);
Kato

34

在以上任何答案中均未提及Object.assign。

let cloned = Object.assign({}, source);

如果您使用的是ES6,则可以使用传播运算符:

let cloned = { ... source };

参考:https : //developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign


28
请注意,这两个都是浅表克隆,对象上的散布运算符需要node.js 8+。
jlh

1
正如@jlh所提到的,这不适用于嵌套对象。:你可以阅读更多关于这种方法的深克隆问题这里developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/...
Shnd

正如Shnd所说,这个答案是不正确的。对于深度克隆,我们需要使用其他替代方法,因为Object.assign()复制属性值。如果源值是对对象的引用,则仅复制该引用值。
troy

1
这不会克隆,它仅引用原始对象,这意味着如果您更改某些内容,它将归主所有
Aidan Welch

@AidanWelch我认为这是不正确的。let a = {x:1}; let b = Object.assign({}, a); a.x = 2; a.y = 1; console.log(a, b);
Miguel Pynto


10

进行通用但有用的克隆操作很困难,因为应该递归克隆哪些内容以及应该复制哪些内容取决于特定对象的工作方式。

可能有用的是

function clone(x)
{
    if (x === null || x === undefined)
        return x;
    if (typeof x.clone === "function")
        return x.clone();
    if (x.constructor == Array)
    {
        var r = [];
        for (var i=0,n=x.length; i<n; i++)
            r.push(clone(x[i]));
        return r;
    }
    return x;
}

在这段代码中,逻辑是

  • 如果是nullundefined仅返回相同的值(需要特殊情况,因为尝试查看是否存在clone方法是错误的)
  • 对象有clone方法吗?然后用那个
  • 对象是数组吗?然后执行递归克隆操作
  • 否则就返回相同的值

此克隆功能应允许轻松实现自定义克隆方法……例如

function Point(x, y)
{
    this.x = x;
    this.y = y;

    ...
}

Point.prototype.clone = function()
{
    return new Point(this.x, this.y);
};



function Polygon(points, style)
{
    this.points = points;
    this.style = style;

    ...
}

Polygon.prototype.clone = function()
{
    return new Polygon(clone(this.points),
                       this.style);
};

当在对象中知道特定数组的正确克隆操作只是浅表副本时,可以调用values.slice()而不是clone(values)

例如,在上面的代码中,我明确要求克隆多边形对象将克隆点,但将共享相同的样式对象。如果我也想克隆样式对象,那么我可以通过clone(this.style)


1
+1表示对象需要自己实现.clone方法。这是处理克隆对象的最佳方法。
雷诺斯

3
if (x.clone)应该是if (typeof x.clone === 'function')
Yanick Rochon

@YanickRochon:谢谢,固定。抱歉,在某种程度上我没有注意到此评论……
6502

9

没有用于克隆对象的本机方法。下划线工具_.clone是一个浅表克隆。

_.clone = function(obj) {
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

它可以切片或扩展它。

这是 _.extend

// extend the obj (first parameter)
_.extend = function(obj) {
  // for each other parameter
  each(slice.call(arguments, 1), function(source) {
    // loop through all properties of the other objects
    for (var prop in source) {
      // if the property is not undefined then add it to the object.
      if (source[prop] !== void 0) obj[prop] = source[prop];
    }
  });
  // return the object (first parameter)
  return obj;
};

扩展简单地遍历所有项目,并创建一个包含项目的新对象。

您可以根据需要推出自己的幼稚实现

function clone(o) {
  var ret = {};
  Object.keys(o).forEach(function (val) {
    ret[val] = o[val];
  });
  return ret;
}

有充分的理由要避免深度克隆,因为无法克隆闭包。

我个人已经问过一个问题deep cloning objects before,得出的结论是您只是不这样做。

我的建议是使用underscore,它是_.clone浅克隆的方法


9

对于浅表副本,我喜欢使用reduce模式(通常在模块等中),如下所示:

var newObject = Object.keys(original).reduce(function (obj, item) {
    obj[item] = original[item];
    return obj;
},{});

这是几个选项的jsperf:http ://jsperf.com/shallow-copying


简单,优雅,辉煌。期待您的其他SO贡献^ _ ^
谢谢您

7

这是个老问题,但答案比迄今为止建议的要优雅得多。使用内置的utils._extend:

var extend = require("util")._extend;

var varToCopy = { test: 12345, nested: { val: 6789 } };

var copiedObject = extend({}, varToCopy);

console.log(copiedObject);

// outputs:
// { test: 12345, nested: { val: 6789 } }

请注意,第一个参数与空对象{}一起使用-这告诉我们扩展了复制的对象需要复制到新对象。如果将现有对象用作第一个参数,则第二个(以及所有后续)参数将在第一个参数变量上进行深度合并复制。

使用上面的示例变量,您还可以执行以下操作:

var anotherMergeVar = { foo: "bar" };

extend(copiedObject, { anotherParam: 'value' }, anotherMergeVar);

console.log(copiedObject);

// outputs:
// { test: 12345, nested: { val: 6789 }, anotherParam: 'value', foo: 'bar' }

非常方便的实用程序,尤其是在我习惯于在AngularJS和jQuery中进行扩展的地方。

希望这对别人有帮助;对象引用覆盖是一种苦难,这每次都能解决!


1
现在不推荐使用_extend。请改用Object.assign():developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/…–
MacroMan,

7

您也可以使用lodash。它具有一个clonecloneDeep方法。

var _= require('lodash');

var objects = [{ 'a': 1 }, { 'b': 2 }];

var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);

1

根据要对克隆对象进行的操作,可以利用javascript的原型继承机制,并通过以下方式实现某种程度的克隆对象:

var clonedObject = Object.create(originalObject);

请记住,这不是完整的克隆-更好或更坏。

这样做的好处是,您实际上没有复制对象,因此内存占用空间很低。

关于此方法,需要记住一些棘手的事情:原型链中定义的属性的迭代有时会有所不同,并且对原始对象的任何更改也会影响克隆的对象,除非该属性本身也已设置。 。


3
这是如此荒谬的危险,完全不同于克隆。var a = {foo: "bar"}, b = Object.create(a); a.foo = "broken"; console.log(b.foo); // "broken";
谢谢您

@naomik:b.foo = "working"; console.log(a.foo); // still "broken";当然必须意识到原始对象的更改将反映在“克隆”中,而“克隆”中的更改将不会反映在原始对象中,但是我不会说这很危险–这一切都取决于您要对克隆做什么
VoxPelli 2014年

@naomik:我对JS还是很陌生,所以我想要一些精度。您喜欢的olli-k提出的浅表克隆(恕我直言,但我可能错了)似乎与voxpelli提出的解决方案具有相同的局限性。原始的和浅层的关闭也将共享相同的“项目”,更新原始中的项目也会影响浅层克隆。要么?
bmorin

@bmornin,如果您使用Olli K的技术,则修改原始对象不会导致新对象的更改。
谢谢您

1
@VoxPelli一个“克隆”的整个目的是从原来的对象分离的变化,因此该方法更危险。
道格2015年

1

我实现了完整的深层副本。我相信它是通用克隆方法的最佳选择,但它不能处理循环引用。

用法示例:

parent = {'prop_chain':3}
obj = Object.create(parent)
obj.a=0; obj.b=1; obj.c=2;

obj2 = copy(obj)

console.log(obj, obj.prop_chain)
// '{'a':0, 'b':1, 'c':2} 3
console.log(obj2, obj2.prop_chain)
// '{'a':0, 'b':1, 'c':2} 3

parent.prop_chain=4
obj2.a = 15

console.log(obj, obj.prop_chain)
// '{'a':0, 'b':1, 'c':2} 4
console.log(obj2, obj2.prop_chain)
// '{'a':15, 'b':1, 'c':2} 4

代码本身:

此代码使用其原型复制对象,还复制函数(可能对某人有用)。

function copy(obj) {
  // (F.prototype will hold the object prototype chain)
  function F() {}
  var newObj;

  if(typeof obj.clone === 'function')
    return obj.clone()

  // To copy something that is not an object, just return it:
  if(typeof obj !== 'object' && typeof obj !== 'function' || obj == null)
    return obj;

  if(typeof obj === 'object') {    
    // Copy the prototype:
    newObj = {}
    var proto = Object.getPrototypeOf(obj)
    Object.setPrototypeOf(newObj, proto)
  } else {
    // If the object is a function the function evaluate it:
    var aux
    newObj = eval('aux='+obj.toString())
    // And copy the prototype:
    newObj.prototype = obj.prototype
  }

  // Copy the object normal properties with a deep copy:
  for(var i in obj) {
    if(obj.hasOwnProperty(i)) {
      if(typeof obj[i] !== 'object')
        newObj[i] = obj[i]
      else
        newObj[i] = copy(obj[i])
    }
  }

  return newObj;
}

有了这个副本,我无法找到原始副本和复制副本之间的任何区别,除非原始副本在其构造上使用了闭包,所以我认为它是一个很好的实现。

希望对您有所帮助


1

对于数组,可以使用

var arr = [1,2,3]; 
var arr_2 = arr ; 

print ( arr_2 ); 

arr = arr.slice(0);

print ( arr ); 

arr[1]=9999; 

print ( arr_2 ); 

1

JavaScript中的对象和数组使用按引用调用,如果更新复制的值,它可能会反映在原始对象上。为了防止这种情况,您可以使用lodash库cloneDeep方法run命令对对象进行深度克隆,以防止传递引用。

npm安装lodash

const ld = require('lodash')
const objectToCopy = {name: "john", age: 24}
const clonedObject = ld.cloneDeep(objectToCopy)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.