Каков наиболее эффективный способ клонирования объекта JavaScript? Я видел, obj = eval(uneval(o));
как используется, но это нестандартно и поддерживается только Firefox .
Я сделал что-то вроде, obj = JSON.parse(JSON.stringify(o));
но поставил под сомнение эффективность.
Я также видел рекурсивные функции копирования с различными недостатками.
Я удивлен, что канонического решения не существует.
Родное глубокое клонирование
Он называется «структурированное клонирование», работает экспериментально в Node 11 и более поздних версиях и, надеюсь, появится в браузерах. Смотрите этот ответ для более подробной информации.
Быстрое клонирование с потерей данных - JSON.parse / stringify
Если вы не используете Date
с, функцией, undefined
, Infinity
, Regexps, карты, наборы, Blobs, списки файлов, ImageDatas разреженных массивов, типизированные массивы или другими сложными типами в пределах вашего объекта, очень простой один вкладыш к глубоким клонировать объект:
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'
re: /.*/, // lost
}
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()
См ответ Corban для ориентиров.
Надежное клонирование с использованием библиотеки
Поскольку клонирование объектов не является тривиальным (сложные типы, циклические ссылки, функции и т. Д.), Большинство основных библиотек предоставляют функции для клонирования объектов. Не изобретайте колесо - если вы уже используете библиотеку, проверьте, есть ли у нее функция клонирования объектов. Например,
- лодаш -
cloneDeep
; может быть импортирован отдельно через модуль lodash.clonedeep и, вероятно, является вашим лучшим выбором, если вы еще не используете библиотеку, которая обеспечивает функцию глубокого клонирования - AngularJS -
angular.copy
- jQuery -
jQuery.extend(true, { }, oldObject)
;.clone()
только клоны DOM-элементов
ES6
Для полноты обратите внимание, что ES6 предлагает два механизма поверхностного копирования: Object.assign()
и синтаксис распространения . который копирует значения всех перечисляемых собственных свойств из одного объекта в другой. Например:
var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1}; // Spread Syntax
.clone()
, а это не тот код, который нужно используя в этом контексте). К сожалению, этот вопрос подвергся стольким пересмотрам, что первоначальная дискуссия уже не очевидна! Пожалуйста, просто следуйте советам Корбана и напишите цикл или скопируйте свойства непосредственно в новый объект, если вам нужна скорость. Или проверьте это сами! Проверьте этот тест: http://jsben.ch/#/bWfk9
В моих предыдущих тестах, где скорость была главной проблемой, я обнаружил
JSON.parse(JSON.stringify(obj))
быть самым медленным способом глубокого клонирования объекта (это медленнее, чем jQuery.extend с deep
установленным флагом true на 10-20%).
jQuery.extend довольно быстро работает, когда установлен deep
флаг false
(мелкий клон). Это хороший вариант, потому что он включает некоторую дополнительную логику для проверки типа и не копирует неопределенные свойства и т. Д., Но это также немного замедлит работу.
Если вы знаете структуру объектов, которые вы пытаетесь клонировать, или можете избежать вложенных массивов, вы можете написать простой for (var i in obj)
цикл для клонирования вашего объекта при проверке hasOwnProperty, и он будет намного быстрее, чем jQuery.
Наконец, если вы пытаетесь клонировать известную структуру объекта в горячем цикле, вы можете получить НАМНОГО БОЛЬШЕ ЭФФЕКТИВНОСТИ, просто вставив процедуру клонирования и вручную создав объект.
for..in
Механизмы трассировки JavaScript не справляются с оптимизацией циклов, и проверка hasOwnProperty также замедлит вас. Ручное клонирование, когда скорость абсолютно необходима.
var clonedObject = {
knownProp: obj.knownProp,
..
}
Остерегайтесь использования JSON.parse(JSON.stringify(obj))
метода для Date
объектов - JSON.stringify(new Date())
возвращает строковое представление даты в формате ISO, которое JSON.parse()
не преобразуется обратно в Date
объект. Смотрите этот ответ для более подробной информации .
Кроме того, обратите внимание, что, по крайней мере, в Chrome 65, клонирование по-родному не подходит. По словам JSPerf, выполнение нативного клонирования путем создания новой функции почти в 800 раз медленнее, чем при использовании JSON.stringify, который невероятно быстр по всем направлениям.
Если вы используете Javascript ES6, попробуйте этот собственный метод для клонирования или поверхностного копирования.
Object.assign({}, obj);
keys
из вашего object
, у которого есть functions
их значения, потому что JSON
он не поддерживает функции. JSON.parse(JSON.stringify(obj))
объектов Date также преобразует дату обратно в UTC в строковом представлении в формате ISO8601 . Предполагая, что в вашем объекте есть только переменные, а не какие-либо функции, вы можете просто использовать:
var newObject = JSON.parse(JSON.stringify(oldObject));
JSON
он реализован в собственном коде (в большинстве браузеров), это будет значительно быстрее, чем при использовании любого другого решения глубокого копирования на основе javascript, и иногда может быть быстрее, чем метод поверхностного копирования на основе javascript (см. Jsperf.com/cloning). -ан-объект / 79 ). JSON.stringify({key: undefined}) //=> "{}"
Date
объекты, которые хранятся внутри объекта, преобразовав их в строковую форму. Структурированное клонирование
Стандарт HTML включает внутренний структурированный алгоритм клонирования / сериализации, который может создавать глубокие клоны объектов. Он по-прежнему ограничен определенными встроенными типами, но в дополнение к нескольким типам, поддерживаемым JSON, он также поддерживает Dates, RegExps, Карты, Наборы, BLOB-объекты, FileLists, ImageDatas, разреженные массивы, Typed Arrays и, возможно, больше в будущем. , Он также сохраняет ссылки в клонированных данных, что позволяет поддерживать циклические и рекурсивные структуры, которые могут вызвать ошибки для JSON.
Поддержка в Node.js: экспериментальная
history.pushState()
и history.replaceState()
методы как синхронно установить history.state
в структурированном клон первого аргумента. Немного странно, но это работает. Я обновляю свой ответ сейчас. Если бы не было встроенного, вы можете попробовать:
function clone(obj) {
if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
return obj;
if (obj instanceof Date)
var temp = new obj.constructor(); //or new Date(obj);
else
var temp = obj.constructor();
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
obj['isActiveClone'] = null;
temp[key] = clone(obj[key]);
delete obj['isActiveClone'];
}
}
return temp;
}
Эффективный способ клонирования (не глубокого клонирования) объекта в одну строку кода
Object.assign
Метод является частью 2015 (ES6) стандарта ECMAScript и делает именно то , что вам нужно.
var clone = Object.assign({}, obj);
Метод Object.assign () используется для копирования значений всех перечисляемых собственных свойств из одного или нескольких исходных объектов в целевой объект.
Polyfill для поддержки старых браузеров:
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;
}
});
}
Код:
// 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 obj = {}
иobj.a = obj
from.constructor
, Date
к примеру. Как будет if
достигнут третий тест, если второй if
тест будет успешным и вызовет возврат функции (с тех пор Date != Object && Date != Array
)? Это то, что я использую:
function cloneObject(obj) {
var clone = {};
for(var i in obj) {
if(typeof(obj[i])=="object" && obj[i] != null)
clone[i] = cloneObject(obj[i]);
else
clone[i] = obj[i];
}
return clone;
}
cloneObject({ name: null })
=>{"name":{}}
typeof null > "object"
но Object.keys(null) > TypeError: Requested keys of a value that is not an object.
измените условие наif(typeof(obj[i])=="object" && obj[i]!=null)
Глубокое копирование по производительности: от лучшего к худшему
- Переназначение "=" (строковые массивы, только числовые массивы)
- Slice (строковые массивы, только числовые массивы)
- Конкатенация (строковые массивы, только числовые массивы)
- Пользовательская функция: цикл или рекурсивное копирование
- jQuery's $ .extend
- JSON.parse (строковые массивы, числовые массивы, только объектные массивы)
- Underscore.js 's _.clone (строковые массивы, только числовые массивы)
- Lo-Dash's _.cloneDeep
Глубокая копия массива строк или чисел (один уровень - без ссылочных указателей):
Когда массив содержит числа и строки - такие функции, как .slice (), .concat (), .splice (), оператор присваивания "=" и функция клона Underscore.js; сделает глубокую копию элементов массива.
Где переназначение имеет самую быструю производительность:
var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];
И .slice () имеет лучшую производительность, чем .concat (), http://jsperf.com/duplicate-array-slice-vs-concat/3
var arr1 = ['a', 'b', 'c']; // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0); // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat(); // Becomes arr2b = ['a', 'b', 'c'] - deep copy
Глубокая копия массива объектов (два или более уровня - ссылочные указатели):
var arr1 = [{object:'a'}, {object:'b'}];
Напишите пользовательскую функцию (имеет более высокую производительность, чем $ .extend () или JSON.parse):
function copy(o) {
var out, v, key;
out = Array.isArray(o) ? [] : {};
for (key in o) {
v = o[key];
out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
}
return out;
}
copy(arr1);
Используйте сторонние утилиты:
$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash
Где jQuery $ .extend имеет лучшую производительность:
out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
var clone = function() {
var newObj = (this instanceof Array) ? [] : {};
for (var i in this) {
if (this[i] && typeof this[i] == "object") {
newObj[i] = this[i].clone();
}
else
{
newObj[i] = this[i];
}
}
return newObj;
};
Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});
Глубокое копирование объектов в JavaScript (я думаю, лучший и самый простой)
1. Использование JSON.parse (JSON.stringify (object));
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 _.cloneDeep link 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. Использование 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.)
JSBEN.CH Performance Benchmarking Playground 1 ~ 3 http://jsben.ch/KVQLd
Object.assign()
не выполняет глубокую копиюlet data = {title:["one", "two"]}; let tmp = cloneObject(data); tmp.title.pop();
он бросает: TypeError: tmp.title.pop is not a function
(конечно, pop () работает нормально, если я просто do let tmp = data
; но тогда я не могу изменить tmp, не влияя на данные)Есть библиотека (называемая «клон») , которая делает это довольно хорошо. Он обеспечивает наиболее полное рекурсивное клонирование / копирование произвольных известных мне объектов. Он также поддерживает циклические ссылки, которые пока не охвачены другими ответами.
Вы также можете найти его на npm . Может использоваться как для браузера, так и для Node.js.
Вот пример того, как его использовать:
Установите его с
npm install clone
или упакуй это с Эндером .
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' } }
(Отказ от ответственности: я автор библиотеки.)
JSON.parse(JSON.stringify(obj))
? 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) Подчеркиваем и загружаем:
var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy
Надеюсь, что это поможет ...
Object.assign
это не глубокая копия. Пример: var x = { a: { b: "c" } }; var y = Object.assign({}, x); x.a.b = "d"
. Если бы это была глубокая копия, y.a.b
все равно было бы c
, но это сейчас d
. Я знаю, что это старый пост, но я подумал, что это может помочь следующему человеку, который спотыкается.
Пока вы не назначаете объект чему-либо, он не сохраняет ссылки в памяти. Таким образом, чтобы создать объект, которым вы хотите поделиться с другими объектами, вам нужно создать фабрику следующим образом:
var a = function(){
return {
father:'zacharias'
};
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);
const defaultFoo = { a: { b: 123 } };
вы можете пойти const defaultFoo = () => ({ a: { b: 123 } };
и ваша проблема решена. Тем не менее, это действительно не ответ на вопрос. Это могло бы иметь больше смысла как комментарий к вопросу, а не как полный ответ. Если вы используете его, в библиотеке Underscore.js есть метод clone .
var newObject = _.clone(oldObject);
.clone(...)
метод служебной библиотеки . Они есть в каждой крупной библиотеке, а повторяющиеся краткие, не детализированные ответы бесполезны для большинства посетителей, которые не будут использовать эту конкретную библиотеку. Вот версия ответа 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://stackoverflow.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();
};
}
Следующее создает два экземпляра одного и того же объекта. Я нашел это и использую это в настоящее время. Это просто и легко в использовании.
var objToCreate = JSON.parse(JSON.stringify(cloneThis));
Крокфорд предлагает (и я предпочитаю) использовать эту функцию:
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
var newObject = object(oldObject);
Это вкратце, работает как положено, и вам не нужна библиотека.
РЕДАКТИРОВАТЬ:
Это polyfill для 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
var extendObj = function(childObj, parentObj) { var tmpObj = function () {} tmpObj.prototype = parentObj.prototype; childObj.prototype = new tmpObj(); childObj.prototype.constructor = childObj; };
... davidshariff.com/blog/javascript-inheritance-patternsУ Lodash есть хороший метод _.cloneDeep (value) :
var objects = [{ 'a': 1 }, { 'b': 2 }];
var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
.clone(...)
метод служебной библиотеки . Они есть в каждой крупной библиотеке, а повторяющиеся краткие, не детализированные ответы бесполезны для большинства посетителей, которые не будут использовать эту конкретную библиотеку. _.merge({}, objA)
. Если бы только lodash не мутировал объекты в первую очередь, тогда clone
функция была бы не нужна. function clone(obj)
{ var clone = {};
clone.prototype = obj.prototype;
for (property in obj) clone[property] = obj[property];
return clone;
}
Однострочная копия мелкой копии ( ECMAScript 5-е издание ):
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 6th edition , 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
Object.keys
пропускает не перечисляемые и унаследованные свойства. Кроме того, он теряет дескрипторы свойств, выполняя прямое присваивание. Просто потому, что я не видел упоминания AngularJS и думал, что люди могут захотеть узнать ...
angular.copy
также предоставляет метод глубокого копирования объектов и массивов.
angular.extend({},obj);
jQuery.extend
и angular.extend
оба являются мелкими копиями. angular.copy
это глубокая копия. Кажется, еще нет идеального оператора глубокого клонирования для массивоподобных объектов. Как показано в приведенном ниже коде, клонер jQuery Джона Резига превращает массивы с нечисловыми свойствами в объекты, которые не являются массивами, а JSON-клонер RegDwight удаляет нечисловые свойства. Следующие тесты иллюстрируют эти пункты в нескольких браузерах:
function jQueryClone(obj) {
return jQuery.extend(true, {}, obj)
}
function JSONClone(obj) {
return JSON.parse(JSON.stringify(obj))
}
var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);
alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
"\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
"\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
"\nAnd what are the JSONClone names? " + JSONCopy.names)
У меня есть два хороших ответа в зависимости от того, является ли ваша цель клонировать «простой старый объект JavaScript» или нет.
Предположим также, что вы намереваетесь создать законченный клон без ссылок прототипов на исходный объект. Если вас не интересует полный клон, вы можете использовать многие из подпрограмм Object.clone (), представленных в некоторых других ответах (шаблон Крокфорда).
Для простых старых объектов JavaScript надежный и надежный способ клонирования объекта в современных средах выполнения довольно прост:
var clone = JSON.parse(JSON.stringify(obj));
Обратите внимание, что исходный объект должен быть чистым объектом JSON. То есть все его вложенные свойства должны быть скалярами (например, логическое значение, строка, массив, объект и т. Д.). Любые функции или специальные объекты, такие как RegExp или Date, не будут клонированы.
Это эффективно? Черт возьми, да. Мы перепробовали все виды методов клонирования, и это работает лучше всего. Я уверен, что какой-нибудь ниндзя мог бы придумать более быстрый метод. Но я подозреваю, что мы говорим о предельной прибыли.
Этот подход просто прост и легко реализуем. Оберните его в удобную функцию, и если вам действительно нужно выжать некоторую выгоду, сделайте это позже.
Теперь для непростых объектов JavaScript не существует простого ответа. Фактически, это не может быть из-за динамической природы функций JavaScript и состояния внутреннего объекта. Глубокое клонирование структуры JSON с функциями внутри требует повторного создания этих функций и их внутреннего контекста. И у JavaScript просто нет стандартизированного способа сделать это.
Правильный способ сделать это, опять же, через удобный метод, который вы объявляете и повторно используете в своем коде. Удобный метод может быть наделен некоторым пониманием ваших собственных объектов, чтобы вы могли убедиться, что правильно воссоздали график в новом объекте.
Мы написаны по-своему, но лучший общий подход, который я видел, описан здесь:
http://davidwalsh.name/javascript-clone
Это правильная идея. Автор (Дэвид Уолш) прокомментировал клонирование обобщенных функций. Это то, что вы можете выбрать, в зависимости от вашего варианта использования.
Основная идея заключается в том, что вам нужно специально обрабатывать создание ваших функций (или, так сказать, прототипов) для каждого типа. Здесь он предоставил несколько примеров для RegExp и Date.
Этот код не только краткий, но и очень читаемый. Это довольно легко расширить.
Это эффективно? Черт возьми, да. Учитывая, что цель состоит в том, чтобы создать настоящий клон глубокой копии, вам придется пройтись по элементам графа исходного объекта. При таком подходе вы можете настроить, какие дочерние элементы обрабатывать, и как вручную обрабатывать пользовательские типы.
Итак, поехали. Два подхода. Оба эффективны, на мой взгляд.
Как правило, это не самое эффективное решение, но оно делает то, что мне нужно. Простые тестовые случаи ниже ...
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
AngularJS
Ну, если вы используете угловой, вы могли бы сделать это тоже
var newObject = angular.copy(oldObject);
Я не согласен с ответом с наибольшим количеством голосов здесь . Рекурсивный Deep Clone это гораздо быстрее , чем JSON.parse (JSON.stringify (OBJ)) подход упоминается.
- Jsperf оценивает это номер один здесь: https://jsperf.com/deep-copy-vs-json-stringify-json-parse/5
- Jsben из приведенного выше ответа обновлен, чтобы показать, что рекурсивный глубокий клон превосходит все остальные упомянутые: http://jsben.ch/13YKQ
А вот функция для быстрого ознакомления:
function cloneDeep (o) {
let newO
let i
if (typeof o !== 'object') return o
if (!o) return o
if (Object.prototype.toString.apply(o) === '[object Array]') {
newO = []
for (i = 0; i < o.length; i += 1) {
newO[i] = cloneDeep(o[i])
}
return newO
}
newO = {}
for (i in o) {
if (o.hasOwnProperty(i)) {
newO[i] = cloneDeep(o[i])
}
}
return newO
}
if(o instanceof Date) return new Date(o.valueOf());
после проверки на null `// 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;
};
Только тогда, когда вы можете использовать ECMAScript 6 или транспортеры .
Особенности:
- Не будет вызывать геттер / сеттер при копировании.
- Сохраняет геттер / сеттер.
- Сохраняет прототип информации.
- Работает как с объектно-буквальным, так и с функциональным стилем записи ОО .
Код:
function clone(target, source){
for(let key in source){
// Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
let descriptor = Object.getOwnPropertyDescriptor(source, key);
if(descriptor.value instanceof String){
target[key] = new String(descriptor.value);
}
else if(descriptor.value instanceof Array){
target[key] = clone([], descriptor.value);
}
else if(descriptor.value instanceof Object){
let prototype = Reflect.getPrototypeOf(descriptor.value);
let cloneObject = clone({}, descriptor.value);
Reflect.setPrototypeOf(cloneObject, prototype);
target[key] = cloneObject;
}
else {
Object.defineProperty(target, key, descriptor);
}
}
let prototype = Reflect.getPrototypeOf(source);
Reflect.setPrototypeOf(target, prototype);
return target;
}
Для людей, которые хотят использовать 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;
});
}
eval()
это вообще плохая идея, потому что многие оптимизаторы движка Javascript вынуждены отключаться при работе с переменными, которые устанавливаются черезeval
. Просто наличиеeval()
в вашем коде может привести к снижению производительности.JSON
метод потеряет все типы Javascript, которые не имеют эквивалента в JSON. Например:JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))
сгенерирует{a: null, b: null, c: null, g: false}