Каков наиболее эффективный способ глубокого клонирования объекта в JavaScript? - PullRequest
5052 голосов
/ 23 сентября 2008

Какой самый эффективный способ клонировать объект JavaScript? Я видел, как используется obj = eval(uneval(o));, но это нестандартно и поддерживается только Firefox .

Я делал такие вещи, как obj = JSON.parse(JSON.stringify(o));, но ставлю под сомнение эффективность.

Я также видел функции рекурсивного копирования с различными недостатками.
Я удивлен, что канонического решения не существует.

Ответы [ 74 ]

2 голосов
/ 04 августа 2015

Требуются новые браузеры, но ...

Давайте расширим нативный объект и получим real .extend();

Object.defineProperty(Object.prototype, 'extend', {
    enumerable: false,
    value: function(){
        var that = this;

        Array.prototype.slice.call(arguments).map(function(source){
            var props = Object.getOwnPropertyNames(source),
                i = 0, l = props.length,
                prop;

            for(; i < l; ++i){
                prop = props[i];

                if(that.hasOwnProperty(prop) && typeof(that[prop]) === 'object'){
                    that[prop] = that[prop].extend(source[prop]);
                }else{
                    Object.defineProperty(that, prop, Object.getOwnPropertyDescriptor(source, prop));
                }
            }
        });

        return this;
    }
});

Просто вставьте это перед любым кодом, который использует .extend () для объекта.

Пример:

var obj1 = {
    node1: '1',
    node2: '2',
    node3: 3
};

var obj2 = {
    node1: '4',
    node2: 5,
    node3: '6'
};

var obj3 = ({}).extend(obj1, obj2);

console.log(obj3);
// Object {node1: "4", node2: 5, node3: "6"}
2 голосов
/ 16 июля 2018

Надеюсь, это поможет.

function deepClone(obj) {
    /*
     * Duplicates an object 
     */

    var ret = null;
    if (obj !== Object(obj)) { // primitive types
        return obj;
    }
    if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) { // string objecs
        ret = obj; // for ex: obj = new String("Spidergap")
    } else if (obj instanceof Date) { // date
        ret = new obj.constructor();
    } else
        ret = Object.create(obj.constructor.prototype);

    var prop = null;
    var allProps = Object.getOwnPropertyNames(obj); //gets non enumerables also


    var props = {};
    for (var i in allProps) {
        prop = allProps[i];
        props[prop] = false;
    }

    for (i in obj) {
        props[i] = i;
    }

    //now props contain both enums and non enums 
    var propDescriptor = null;
    var newPropVal = null; // value of the property in new object
    for (i in props) {
        prop = obj[i];
        propDescriptor = Object.getOwnPropertyDescriptor(obj, i);

        if (Array.isArray(prop)) { //not backward compatible
            prop = prop.slice(); // to copy the array
        } else
        if (prop instanceof Date == true) {
            prop = new prop.constructor();
        } else
        if (prop instanceof Object == true) {
            if (prop instanceof Function == true) { // function
                if (!Function.prototype.clone) {
                    Function.prototype.clone = function() {
                        var that = this;
                        var temp = function tmp() {
                            return that.apply(this, arguments);
                        };
                        for (var ky in this) {
                            temp[ky] = this[ky];
                        }
                        return temp;
                    }
                }
                prop = prop.clone();

            } else // normal object 
            {
                prop = deepClone(prop);
            }

        }

        newPropVal = {
            value: prop
        };
        if (propDescriptor) {
            /*
             * If property descriptors are there, they must be copied
             */
            newPropVal.enumerable = propDescriptor.enumerable;
            newPropVal.writable = propDescriptor.writable;

        }
        if (!ret.hasOwnProperty(i)) // when String or other predefined objects
            Object.defineProperty(ret, i, newPropVal); // non enumerable

    }
    return ret;
}

https://github.com/jinujd/Javascript-Deep-Clone

2 голосов
/ 05 июня 2019

Существует три различных способа клонирования объектов в Javascript.

1: глубокое копирование с использованием итерации

function iterationCopy(src) {
  let target = {};
  for (let prop in src) {
    if (src.hasOwnProperty(prop)) {
      target[prop] = src[prop];
    }
  }
  return target;
}
const source = {a:1, b:2, c:3};
const target = iterationCopy(source);
console.log(target); // {a:1, b:2, c:3}
// Check if clones it and not changing it
source.a = 'a';
console.log(source.a); // 'a'
console.log(target.a); // 1

Итак, как вы видите, это работает!

Теперь давайте остановимся на втором решении, которое действительно более элегантно, но более ограничено в использовании.

2: преобразование в JSON и обратно

function jsonCopy(src) {
  return JSON.parse(JSON.stringify(src));
}
const source = {a:1, b:2, c:3};
const target = jsonCopy(source);
console.log(target); // {a:1, b:2, c:3}
// Check if clones it and not changing it
source.a = 'a';
console.log(source.a); // 'a'
console.log(target.a); // 1

Примечание. Будьте осторожны при использовании этого метода, поскольку ваш исходный объект ДОЛЖЕН быть безопасным в JSON. Поэтому для обеспечения безопасности может потребоваться какая-то обработка исключений, если исходный объект не может быть преобразован в JSON.

3: Использование Object.assign

Обновление: у этого метода есть недостаток, заключающийся в том, что он делает только поверхностную копию. Это означает, что вложенные свойства все еще будут скопированы по ссылке. Будьте осторожны с этим.

Этот способ является лучшим и самым безопасным способом, который я лично использую в своих проектах. Он использует встроенный статический метод для объекта Object и обрабатывается и обеспечивается языком. Так что используйте это!

function bestCopyEver(src) {
  return Object.assign({}, src);
}
const source = {a:1, b:2, c:3};
const target = bestCopyEver(source);
console.log(target); // {a:1, b:2, c:3}
// Check if clones it and not changing it
source.a = 'a';
console.log(source.a); // 'a'
console.log(target.a); // 1
1 голос
/ 19 мая 2019

Мой сценарий был немного другим. У меня был объект с вложенными объектами и функциями. Поэтому Object.assign() и JSON.stringify() не были решениями моей проблемы. Использование сторонних библиотек для меня тоже не вариант.

Поэтому я решил создать простую функцию, использующую встроенные методы для копирования объекта с его буквальными свойствами, вложенными объектами и функциями.

let deepCopy = (target, source) => {
    Object.assign(target, source);
    // check if there's any nested objects
    Object.keys(source).forEach((prop) => {
        /**
          * assign function copies functions and
          * literals (int, strings, etc...)
          * except for objects and arrays, so:
          */
        if (typeof(source[prop]) === 'object') {
            // check if the item is, in fact, an array
            if (Array.isArray(source[prop])) {
                // clear the copied referenece of nested array
                target[prop] = Array();
                // iterate array's item and copy over
                source[prop].forEach((item, index) => {
                    // array's items could be objects too!
                    if (typeof(item) === 'object') {
                        // clear the copied referenece of nested objects
                        target[prop][index] = Object();
                        // and re do the process for nested objects
                        deepCopy(target[prop][index], item);
                    } else {
                        target[prop].push(item);
                    }
                });
            // otherwise, treat it as an object
            } else {
                // clear the copied referenece of nested objects
                target[prop] = Object();
                // and re do the process for nested objects
                deepCopy(target[prop], source[prop]);
            }
        }
    });
};

Вот тестовый код:

let a = {
    name: 'Human', 
    func: () => {
        console.log('Hi!');
    }, 
    prop: {
        age: 21, 
        info: {
            hasShirt: true, 
            hasHat: false
        }
    },
    mark: [89, 92, { exam: [1, 2, 3] }]
};

let b = Object();

deepCopy(b, a);

a.name = 'Alien';
a.func = () => { console.log('Wassup!'); };
a.prop.age = 1024;
a.prop.info.hasShirt = false;
a.mark[0] = 87;
a.mark[1] = 91;
a.mark[2].exam = [4, 5, 6];

console.log(a); // updated props
console.log(b);

Что касается проблем, связанных с эффективностью, я считаю, что это самое простое и эффективное решение проблемы, с которой я столкнулся. Буду признателен за любые комментарии по этому алгоритму, которые могут сделать его более эффективным.

1 голос
/ 16 апреля 2019

С предложением нового метода Object.fromEntries () , который поддерживается в более новых версиях некоторых браузеров ( ссылка ). Я хочу внести свой вклад с помощью следующего рекурсивного подхода:

const obj = {
  key1: {key11: "key11", key12: "key12", key13: {key131: 22}},
  key2: {key21: "key21", key22: "key22"},
  key3: "key3",
  key4: [1,2,3, {key: "value"}]
}

const cloneObj = (obj) =>
{
    if (Object(obj) !== obj)
       return obj;
    else if (Array.isArray(obj))
       return obj.map(cloneObj);

    return Object.fromEntries(Object.entries(obj).map(
        ([k,v]) => ([k, cloneObj(v)])
    ));
}

// Clone the original object.
let newObj = cloneObj(obj);

// Make changes on the original object.
obj.key1.key11 = "TEST";
obj.key3 = "TEST";
obj.key1.key13.key131 = "TEST";
obj.key4[1] = "TEST";
obj.key4[3].key = "TEST";

// Display both objects on the console.
console.log("Original object: ", obj);
console.log("Cloned object: ", newObj);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
1 голос
/ 16 августа 2017

Поскольку на этот вопрос много внимания и ответов со ссылкой на встроенные функции, такие как Object.assign или пользовательский код для глубокого клонирования, я хотел бы поделиться некоторыми библиотеками для глубокого клонирования,

1. esclone

npm install --savedev esclone https://www.npmjs.com/package/esclone

Пример использования в ES6:

import esclone from "esclone";

const rockysGrandFather = {
  name: "Rockys grand father",
  father: "Don't know :("
};
const rockysFather = {
  name: "Rockys Father",
  father: rockysGrandFather
};

const rocky = {
  name: "Rocky",
  father: rockysFather
};

const rockyClone = esclone(rocky);

Пример использования в ES5:

var esclone = require("esclone")
var foo = new String("abcd")
var fooClone = esclone.default(foo)
console.log(fooClone)
console.log(foo === fooClone)

2. глубокая копия

npm установить глубокую копию https://www.npmjs.com/package/deep-copy

* * 1 022 Пример: * 1 023 *
var dcopy = require('deep-copy')

// deep copy object 
var copy = dcopy({a: {b: [{c: 5}]}})

// deep copy array 
var copy = dcopy([1, 2, {a: {b: 5}}])

3. Клон-глубоко

$ npm install - сохранить clone-deep https://www.npmjs.com/package/clone-deep

* +1032 * Пример: * 1 033 *
var cloneDeep = require('clone-deep');

var obj = {a: 'b'};
var arr = [obj];

var copy = cloneDeep(arr);
obj.c = 'd';

console.log(copy);
//=> [{a: 'b'}] 

console.log(arr);
0 голосов
/ 31 мая 2019
//  use Object.assign()

const obj = { 
 //whatever your object 
}

const clone = Object.assign({}, obj)
0 голосов
/ 22 января 2017

Лучший и самый современный способ сделать этот клон выглядит следующим образом:

с использованием оператора спреда "..." ES6 Пример:

var clonedObjArray = [...oldObjArray];

таким образом мы распределяем массив по отдельным значениям и помещаем его в новый массив с помощью оператора [].

вот более длинный пример, показывающий, как это работает

let objArray = [ {a:1} , {b:2} ];

let refArray = objArray; // this will just point to the objArray
let clonedArray = [...objArray]; // will clone the array

console.log( "before:" );
console.log( "obj array" , objArray );
console.log( "ref array" , refArray );
console.log( "cloned array" , clonedArray );

objArray[0] = {c:3};

console.log( "after:" );
console.log( "obj array" , objArray ); // [ {c:3} , {b:2} ]
console.log( "ref array" , refArray ); // [ {c:3} , {b:2} ]
console.log( "cloned array" , clonedArray ); // [ {a:1} , {b:2} ]
0 голосов
/ 05 апреля 2019

Если вы используете es6, вы можете просто сделать это с помощью оператора распространения.

let a = {id:1, name:'sample_name'}
let b = {...a};
0 голосов
/ 05 февраля 2019
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.");
}

используйте следующий метод вместо JSON.parse(JSON.stringify(obj)), потому что это медленнее, чем следующий метод

Как правильно клонировать объект JavaScript?

...