javascript - элемента - объекты js




Каков наиболее эффективный способ глубокого клонирования объекта в JavaScript? (20)

Примечание. Это ответ на другой ответ, а не правильный ответ на этот вопрос. Если вы хотите быстро клонировать объекты, пожалуйста, следуйте советам Корбана в их ответе на этот вопрос.

Я хочу отметить, что метод .clone() в jQuery только клонирует элементы DOM. Чтобы клонировать объекты JavaScript, вы должны:

// Shallow copy
var newObject = jQuery.extend({}, oldObject);

// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

Более подробную информацию можно найти в документации jQuery .

Я также хочу отметить, что глубокая копия на самом деле намного умнее, чем показано выше - она ​​способна избежать многих ловушек (например, для глубокого расширения элемента DOM). Он часто используется в ядре jQuery и в плагинах.

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

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

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


Структурированное клонирование

HTML5 определяет внутренний «структурированный» алгоритм клонирования, который может создавать глубокие клоны объектов. Он по-прежнему ограничен определенными встроенными типами, но в дополнение к нескольким типам, поддерживаемым JSON, он также поддерживает Dates, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, разреженные массивы, типизированные массивы и, возможно, больше в будущем , Он также сохраняет ссылки в клонированных данных, что позволяет поддерживать циклические и рекурсивные структуры, которые могут вызывать ошибки для JSON.

Прямая поддержка в браузерах: скоро? 🙂

Браузеры в настоящее время не предоставляют прямой интерфейс для алгоритма структурированного клонирования, но глобальная функция structuredClone() активно обсуждается в whatwg / html # 793 на GitHub и может скоро появиться! Как предлагается в настоящее время, использование его для большинства целей будет таким же простым, как:

const clone = structuredClone(original);

Пока это не будет отправлено, реализации структурированных клонов браузеров будут отображаться только косвенно.

Асинхронное обходное решение: можно использовать. 😕

Более низкий способ создания структурированного клона с существующими API-интерфейсами заключается в публикации данных через один порт MessageChannels . Другой порт выдает событие message со структурированным клоном прикрепленных .data . К сожалению, прослушивание этих событий обязательно асинхронно, а синхронные альтернативы менее практичны.

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

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

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

Синхронные обходные пути: Ужасно! 🤢

Не существует хороших вариантов синхронного создания структурированных клонов. Вот пара непрактичных хаков.

history.pushState() и history.replaceState() создают структурированный клон своего первого аргумента и присваивают это значение history.state . Вы можете использовать это, чтобы создать структурированный клон любого объекта, например:

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

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

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

main();

Хотя синхронно, это может быть очень медленным. Он несет все накладные расходы, связанные с управлением историей браузера. Повторное вызов этого метода может привести к тому, что Chrome станет временно неактуальным.

Конструктор Notification создает структурированный клон связанных с ним данных. Он также пытается отобразить уведомление браузера для пользователя, но это будет беспроблемным, если вы не запросили разрешение на уведомление. Если у вас есть разрешение для других целей, мы немедленно закроем созданное нами уведомление.

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

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

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.close();
  return n.data;
};

main();


AngularJS

Хорошо, если вы используете угловой, вы тоже можете это сделать

var newObject = angular.copy(oldObject);

Вот версия ответа ConroyP выше, которая работает, даже если у конструктора требуются параметры:

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

Эта функция также доступна в моей библиотеке simpleoo .

Редактировать:

Вот более надежная версия (благодаря Джастину МакКэндлесу, теперь это поддерживает циклические ссылки):

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.push(src);
        _copiesVisited.push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.push(src);
    _copiesVisited.push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

Если вы используете его, библиотека Underscore.js имеет метод clone .

var newObject = _.clone(oldObject);

Код:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

Тестовое задание:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);

Предполагая, что у вас есть только переменные, а не какие-либо функции в вашем объекте, вы можете просто использовать:

var newObject = JSON.parse(JSON.stringify(oldObject));

Там есть библиотека (называемая «клон») , которая делает это довольно хорошо. Он обеспечивает наиболее полное рекурсивное клонирование / копирование произвольных объектов, о которых я знаю. Он также поддерживает циркулярные ссылки, которые еще не охватываются другими ответами.

Вы можете найти его и на npm . Он может использоваться как для браузера, так и для Node.js.

Вот пример того, как его использовать:

Установите его с помощью

npm install clone

или упаковать его с помощью Ender .

ender build clone [...]

Вы также можете загрузить исходный код вручную.

Затем вы можете использовать его в своем исходном коде.

var clone = require('clone');

var a = { foo: { bar: 'baz' } };  // inital value of a
var b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';                // change a

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

(Отказ от ответственности: я автор библиотеки.)


Я знаю, что это старый пост, но я подумал, что это может помочь какой-то другой персоне, которая спотыкается.

Пока вы не назначаете объект чему-либо, он не поддерживает ссылки в памяти. Таким образом, чтобы создать объект, который вы хотите разделить между другими объектами, вам необходимо создать такую ​​фабрику:

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);

Cloning объекта всегда вызывало беспокойство в JS, но все это было до ES6, я перечисляю различные способы копирования объекта в JavaScript ниже, представьте, что у вас есть Объект ниже и хотел бы иметь глубокую копию этого:

var obj = {a:1, b:2, c:3, d:4};

Существует несколько способов копирования этого объекта без изменения источника:

1) ES5 +, используя простую функцию для копирования:

function deepCopyObj(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }
    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 this object.");
}

2) ES5 +, используя JSON.parse и JSON.stringify.

var  deepCopyObj = JSON.parse(JSON.stringify(obj));

3) AngularJs:

var  deepCopyObj = angular.copy(obj);

4) jQuery:

var deepCopyObj = jQuery.extend(true, {}, obj);

5) UnderscoreJs & Loadash:

var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy

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


Глубокое копирование объектов в JavaScript (я думаю, самый лучший и самый простой)

1. Использование JSON.parse (JSON.stringify (объект));

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

2. Использование созданного метода

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(obj[i] != null &&  typeof(obj[i])=="object")
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

3. Использование Lo-Dash's _.cloneDeep ссылки lodash

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

4. Использование метода Object.assign ()

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

НО НЕПРАВИЛЬНО КОГДА

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5.Using Underscore.js _.clone link Underscore.js

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

НО НЕПРАВИЛЬНО КОГДА

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

Ссылка medium.com

JSBEN.CH Performance Benchmarking Игровая площадка 1 ~ 3 http://jsben.ch/KVQLd


Крокфорд предлагает (и я предпочитаю) использовать эту функцию:

function object(o) {
    function F() {}
    F.prototype = o;
    return new F();
}

var newObject = object(oldObject);

Это кратковременно, работает так, как ожидалось, и вам не нужна библиотека.

РЕДАКТИРОВАТЬ:

Это полипол для Object.create, так что вы также можете использовать это.

var newObject = Object.create(oldObject);

ПРИМЕЧАНИЕ. Если вы используете некоторые из них, у вас могут возникнуть проблемы с какой-либо итерацией, которые используют hasOwnProperty. Потому что createсоздайте новый пустой объект, который наследует oldObject. Но это все еще полезно и практично для клонирования объектов.

Например, если oldObject.a = 5;

newObject.a; // is 5

но:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false

Вот комплексный метод clone (), который может клонировать любой объект JavaScript. Он обрабатывает почти все случаи:

function clone(src, deep) {

    var toString = Object.prototype.toString;
    if (!src && typeof src != "object") {
        // Any non-object (Boolean, String, Number), null, undefined, NaN
        return src;
    }

    // Honor native/custom clone methods
    if (src.clone && toString.call(src.clone) == "[object Function]") {
        return src.clone(deep);
    }

    // DOM elements
    if (src.nodeType && toString.call(src.cloneNode) == "[object Function]") {
        return src.cloneNode(deep);
    }

    // Date
    if (toString.call(src) == "[object Date]") {
        return new Date(src.getTime());
    }

    // RegExp
    if (toString.call(src) == "[object RegExp]") {
        return new RegExp(src);
    }

    // Function
    if (toString.call(src) == "[object Function]") {

        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function(){
            src.apply(this, arguments);
        });
    }

    var ret, index;
    //Array
    if (toString.call(src) == "[object Array]") {
        //[].slice(0) would soft clone
        ret = src.slice();
        if (deep) {
            index = ret.length;
            while (index--) {
                ret[index] = clone(ret[index], true);
            }
        }
    }
    //Object
    else {
        ret = src.constructor ? new src.constructor() : {};
        for (var prop in src) {
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        }
    }
    return ret;
};

Для людей, которые хотят использовать эту JSON.parse(JSON.stringify(obj))версию, но не теряя объекты Date, вы можете использовать второй аргумент parseметода для преобразования строк в Date:

function clone(obj) {
  var regExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
  return JSON.parse(JSON.stringify(x), function(k, v) {
    if (typeof v === 'string' && regExp.test(v))
      return new Date(v);
    return v;
  });
}

Мелкая копия с одним слоем ( 5-е издание ECMAScript ):

var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

И мелкая копия с одним слоем ( ECMAScript 6-е издание , 2015):

var origin = { foo : {} };
var copy = Object.assign({}, origin);

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

Обычно это не самое эффективное решение, но оно делает то, что мне нужно. Простые тестовые примеры ниже ...

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

    if (obj === null) {
        return obj;
    }

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

Тест циклического массива ...

a = []
a.push("b", "c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

Функциональный тест ...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false

Следующее создает два экземпляра одного и того же объекта. Я нашел его и использую его в настоящее время. Он прост и удобен в использовании.

var objToCreate = JSON.parse(JSON.stringify(cloneThis));

У Lodash хороший lodash.com/docs#cloneDeep :

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

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

// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};

function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }




clone