โหนด: วิธีการโคลนวัตถุ


คำตอบ:


181

สำหรับยูทิลิตี้และคลาสที่ไม่จำเป็นต้องบีบประสิทธิภาพทุกหยดฉันมักจะโกงและใช้ JSON เพื่อทำสำเนาลึก:

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

นี่ไม่ใช่คำตอบเดียวหรือคำตอบที่สวยหรูที่สุด คำตอบอื่น ๆ ทั้งหมดควรได้รับการพิจารณาสำหรับปัญหาคอขวดในการผลิต อย่างไรก็ตามนี่เป็นวิธีแก้ปัญหาที่รวดเร็วและสกปรกค่อนข้างมีประสิทธิภาพและมีประโยชน์ในสถานการณ์ส่วนใหญ่ที่ฉันจะโคลนคุณสมบัติง่ายๆ


2
@djechlin แน่นอนค่ะ ลองใช้ดู: jsfiddle.net/katowulf/E5jC3 (ทดสอบด้วยโหนด 0.10.11) จะไม่สามารถสร้างฟังก์ชันหรือข้อมูลต้นแบบขึ้นมาใหม่ได้ แต่จะได้รับค่าที่ดี
Kato

13
สิ่งนี้จะแปลงวันที่เป็นสตริง
backus

4
@ Backus เช่นเดียวกับวัตถุและฟังก์ชัน
Kato

2
@Kato ทำไมการอ้างอิงแบบวงกลมจึงบ่งบอกว่าไม่จำเป็นต้องทำสำเนาลึก ๆ เป็นสองสิ่งที่ไม่เกี่ยวข้องกัน เป็นไปได้อย่างแน่นอนที่จะเขียนวิธีการโคลนที่ดีสำหรับ NodeJS ที่รองรับการอ้างอิงแบบวงกลมและไม่ถอยกลับไปใช้ JSON เพื่อโคลน github.com/pvorb/node-clone
Samuel Neff

2
ระบุไว้แล้วในความคิดเห็นและในคำอธิบาย คุณไม่สามารถโคลนฟังก์ชันต้นแบบวัตถุหรือสิ่งอื่น ๆ ที่คุณไม่ควรพยายามทำด้วยวิธีโคลนทั่วไป คุณจะต้องมีโค้ดมากกว่าหนึ่งบรรทัดสำหรับสิ่งนั้นและอาจไม่ควรโคลนขยะนั้นต่อไป - ใส่วิธีโคลนในชั้นเรียนของคุณที่รู้วิธีจัดการภายในและทำnewObj = obj.clone(args...);
Kato

36

Object.assign ไม่ได้กล่าวถึงในคำตอบใด ๆ ข้างต้น

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

หากคุณใช้ ES6 คุณสามารถใช้ตัวดำเนินการกระจาย:

let cloned = { ... source };

อ้างอิง: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign


32
โปรดทราบว่าทั้งสองอย่างนี้เป็นโคลนนิ่งตื้นและตัวดำเนินการกระจายบนวัตถุต้องใช้ node.js 8+
jlh

1
ดังที่ @jlh กล่าวถึงสิ่งนี้จะไม่ทำงานกับวัตถุที่ซ้อนกัน คุณสามารถอ่านเพิ่มเติมเกี่ยวกับปัญหาการโคลนลึกของวิธีนี้ได้ที่นี่: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
61

ดังที่ Shnd กล่าวคำตอบนี้ไม่ถูกต้อง สำหรับการโคลนนิ่งแบบลึกเราจำเป็นต้องใช้ทางเลือกอื่นเนื่องจาก Object.assign () คัดลอกค่าคุณสมบัติ หากค่าต้นทางเป็นการอ้างอิงถึงอ็อบเจ็กต์ค่านั้นจะคัดลอกเฉพาะค่าอ้างอิงนั้นเท่านั้น
ทรอย

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

20

มีโมดูลโหนดบางโมดูลอยู่ถ้าคุณไม่ต้องการ "ม้วนของคุณเอง" อันนี้ดูดี: https://www.npmjs.com/package/clone

ดูเหมือนว่ามันจะจัดการทุกอย่างรวมถึงการอ้างอิงแบบวงกลม จากหน้าgithub :

โคลนต้นแบบการโคลนวัตถุอาร์เรย์วัตถุวันที่และวัตถุ RegEx ทุกอย่างจะถูกโคลนซ้ำเพื่อให้คุณสามารถโคลนวันที่ในอาร์เรย์ในออบเจ็กต์ได้ [... ] การอ้างอิงแบบวงกลม? อ๋อ!


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

ในรหัสนี้ตรรกะคือ

  • ในกรณีnullหรือundefinedเพียงแค่ส่งคืนเดียวกัน (จำเป็นต้องใช้กรณีพิเศษเนื่องจากมีข้อผิดพลาดในการพยายามดูว่ามี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วิธีการเอง นี่เป็นวิธีที่ดีที่สุดในการจัดการกับการโคลนนิ่งวัตถุ
Raynos

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

สำหรับสำเนาตื้นฉันชอบใช้รูปแบบการลดขนาด (โดยปกติจะอยู่ในโมดูลหรือมากกว่านั้น) ดังนี้:

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

นี่คือ jsperf สำหรับสองตัวเลือก: http://jsperf.com/shallow-copying


เรียบง่ายสง่างามสดใส ฉันหวังว่าจะมีส่วนร่วมอื่น ๆ ของคุณ^ _ ^
ขอบคุณ

8

คุณสามารถใช้lodash ได้เช่นกัน แต่ก็มีโคลนและcloneDeepวิธี

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

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

หวังว่านี่จะช่วยคนอื่นได้ การเขียนทับการอ้างอิงวัตถุเป็นความทุกข์ยากและสิ่งนี้จะแก้ไขได้ทุกครั้ง!


2
การใช้ _extend ถูกหักค่าเสื่อมราคาแล้ว ใช้ Object.assign () แทน: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
MacroMan

1

ขึ้นอยู่กับสิ่งที่คุณต้องการทำกับวัตถุโคลนของคุณคุณสามารถใช้กลไกการสืบทอดต้นแบบของจาวาสคริปต์และบรรลุวัตถุที่ค่อนข้างโคลนผ่าน:

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

@naomik: ฉันค่อนข้างใหม่กับ JS ดังนั้นฉันจึงต้องการความแม่นยำ โคลนตื้นที่เสนอโดย olli-k และดูเหมือนว่าคุณชอบ (IMHO แต่ฉันอาจคิดผิด) มีข้อ จำกัด เหมือนกันกับโซลูชันที่เสนอโดย voxpelli การปิดแบบดั้งเดิมและแบบตื้นจะใช้ "ไอเท็ม" เหมือนกันการอัปเดตรายการในต้นฉบับจะส่งผลกระทบต่อโคลนนิ่งตื้นด้วย หรือ?
bmorin

@bmornin หากคุณใช้เทคนิคของ Olli K การปรับเปลี่ยนวัตถุดั้งเดิมจะไม่ทำให้เกิดการเปลี่ยนแปลงในวัตถุใหม่
ขอบคุณ

1
@VoxPelli วัตถุประสงค์ทั้งของ "โคลน" คือการเปลี่ยนแปลงที่แยกจากวัตถุเดิมเพราะฉะนั้นวิธีนี้เป็นอันตรายมากขึ้น
Doug

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

ออบเจ็กต์และอาร์เรย์ใน JavaScript ใช้การเรียกโดยการอ้างอิงหากคุณอัปเดตค่าที่คัดลอกมาอาจแสดงถึงออบเจ็กต์ดั้งเดิม เพื่อป้องกันสิ่งนี้คุณสามารถโคลนวัตถุได้ลึกเพื่อป้องกันไม่ให้การอ้างอิงถูกส่งผ่านโดยใช้วิธีการเรียกใช้ lodash library cloneDeep

npm ติดตั้ง lodash

const ld = require('lodash')
const objectToCopy = {name: "john", age: 24}
const clonedObject = ld.cloneDeep(objectToCopy)

0

สำหรับอาร์เรย์สามารถใช้ได้

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

print ( arr_2 ); 

arr = arr.slice (0);

print ( arr ); 

arr[1]=9999; 

print ( arr_2 ); 
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.