Я знаю, что уже есть много ответов и столько комментариев, что они не сработают. Единственный консенсус в том, что он настолько сложный, что никто не сделал для него стандарта . Однако большинство принятых ответов в SO раскрывают «простые трюки», которые широко используются. Итак, для всех нас, как и я, которые не являются экспертами, но хотят писать более безопасный код, хватая немного больше на сложность javascript, я попытаюсь пролить некоторый свет.
Прежде чем загрязнять руки, пусть я уточняю 2 пункта:
Object.assign
делает. for..in
или Object.keys
вводят в заблуждение Создание глубокой копии кажется такой базовой и обычной практикой, которую мы ожидаем найти однострочный или, по крайней мере, быстрый выигрыш посредством простой рекурсии. Мы не ожидаем, что нам понадобится библиотека или напишите пользовательскую функцию из 100 строк.
Когда я впервые прочитал ответ Салакара , я искренне думал, что смогу сделать лучше и проще ( вы можете сравнить его с Object.assign
на x={a:1}, y={a:{b:1}}
). Затем я прочитал ответ the8472 , и я подумал ... нет так легко уйти, что улучшение уже предоставленных ответов не заставит нас далеко.
Давайте дадим глубокую копию и рекурсивную в одно мгновение. Просто подумайте, как (ошибочно) люди анализируют свойства для копирования очень простого объекта.
const y = Object.create(
{ proto : 1 },
{ a: { enumerable: true, value: 1},
[Symbol('b')] : { enumerable: true, value: 1} } )
Object.assign({},y)
> { 'a': 1, Symbol(b): 1 } // All (enumerable) properties are copied
((x,y) => Object.keys(y).reduce((acc,k) => Object.assign(acc, { [k]: y[k] }), x))({},y)
> { 'a': 1 } // Missing a property!
((x,y) => {for (let k in y) x[k]=y[k];return x})({},y)
> { 'a': 1, 'proto': 1 } // Missing a property! Prototype's property is copied too!
Object.keys
будет опускать собственные неперечислимые свойства, собственные свойства с символьными ключами и все свойства прототипа. Это может быть хорошо, если у ваших объектов нет таких. Но имейте в виду, что Object.assign
обрабатывает собственные перечислимые свойства с символьными ключами. Таким образом, ваша пользовательская копия потеряла свой цвет.
for..in
предоставит свойства источника, его прототипа и полной цепи прототипов, если вы этого не захотите (или зная об этом).
Если вы пишете функцию общего назначения, и вы не используете Object.getOwnPropertyDescriptors
, Object.getOwnPropertyNames
, Object.getOwnPropertySymbols
, вы можете использовать слишком много свойств, смешивая свойства прототипа и собственные свойства. ] или Object.getPrototypeOf
, вы, скорее всего, ошибаетесь.
Сначала убедитесь, что вы понимаете, что такое объект Javascript. В Javascript объект создается из его собственных свойств и (родительского) объекта прототипа. Объект прототипа, в свою очередь, состоит из его собственных свойств и прототипа. И т. Д., Определяя цепочку прототипов.
Свойство представляет собой пару ключей (string
или symbol
) и дескрипторов (value
или get
/ set
и атрибутов например enumerable
).
Наконец, существует множество типов объектов . Вы можете захотеть иначе обрабатывать объект Object с объекта Date или объекта Function.
Итак, написав свою глубокую копию, вы должны ответить хотя бы на эти вопросы:
В моем примере я считаю, что только И я сделал объект Вы можете протестировать ее в этом plunker . Это можно использовать следующим образом: object Object
s являются deep , потому что другие объекты, созданные другими конструкторами, могут быть неадекватными для углубленного просмотра. this [] function toType(a) {
// Get fine type (object, array, function, null, error, date ...)
return ({}).toString.call(a).match(/([a-z]+)(:?\])/i)[1];
}
function isDeepObject(obj) {
return "Object" === toType(obj);
}
options
, чтобы выбрать, что копировать (для демонстрационной цели). const options = {nonEnum:true, symbols:true, descriptors: true, proto:true};
Предлагаемая функция
function deepAssign(options) {
return function deepAssignWithOptions (target, ...sources) {
sources.forEach( (source) => {
if (!isDeepObject(source) || !isDeepObject(target))
return;
// Copy source's own properties into target's own properties
function copyProperty(property) {
const descriptor = Object.getOwnPropertyDescriptor(source, property);
//default: omit non-enumerable properties
if (descriptor.enumerable || options.nonEnum) {
// Copy in-depth first
if (isDeepObject(source[property]) && isDeepObject(target[property]))
descriptor.value = deepAssign(options)(target[property], source[property]);
//default: omit descriptors
if (options.descriptors)
Object.defineProperty(target, property, descriptor); // shallow copy descriptor
else
target[property] = descriptor.value; // shallow copy value only
}
}
// Copy string-keyed properties
Object.getOwnPropertyNames(source).forEach(copyProperty);
//default: omit symbol-keyed properties
if (options.symbols)
Object.getOwnPropertySymbols(source).forEach(copyProperty);
//default: omit prototype's own properties
if (options.proto)
// Copy souce prototype's own properties into target prototype's own properties
deepAssign(Object.assign({},options,{proto:false})) (// Prevent deeper copy of the prototype chain
Object.getPrototypeOf(target),
Object.getPrototypeOf(source)
);
});
return target;
}
}
const x = { a: { a: 1 } },
y = { a: { b: 1 } };
deepAssign(options)(x,y); // { a: { a: 1, b: 1 } }