Распространение объекта против Object.assign
Допустим, у меня есть options
переменная, и я хочу установить значение по умолчанию.
Какова польза / недостаток этих двух альтернатив?
Использование объекта распространения
options = {...optionsDefault, ...options};
Или используя Object.assign
options = Object.assign({}, optionsDefault, options);
Это то, что заставило меня задуматься.
16 ответов
Это не обязательно является исчерпывающим.
Синтаксис распространения
options = {...optionsDefault, ...options};
Преимущества:
Если вы создаете код для выполнения в средах без встроенной поддержки, вы можете просто скомпилировать этот синтаксис (в отличие от использования полифилла). (С Вавилоном, например.)
Менее многословно.
Недостатки:
Предложение не стандартизировано. (Подумайте, что бы вы сделали, если бы написали это сейчас, а оно не стандартизировалось.)
Буквально, не динамично.
Object.assign()
options = Object.assign({}, optionsDefault, options);
Преимущества:
Стандартизован.
Динамический. Пример:
var sources = [{a: "A"}, {b: "B"}, {c: "C"}]; options = Object.assign.apply(Object, [{}].concat(sources));
Более многословно.
Недостатки:
- Если вы разрабатываете код для выполнения в средах без встроенной поддержки, вам необходимо выполнить полифилл.
Это то, что заставило меня задуматься.
Это не имеет прямого отношения к тому, что вы спрашиваете. Этот код не использовал Object.assign()
, он использовал код пользователя (object-assign
) это делает то же самое. Похоже, они компилируют этот код с Babel (и связывают его с Webpack), о чем я говорил: синтаксис, который вы можете просто скомпилировать. Очевидно, они предпочли, чтобы object-assign
как зависимость, которая вошла бы в их сборку.
Для эталонного объекта отдых / разворот завершается в ECMAScript 2018 как этап 4. Предложение можно найти здесь.
В большинстве случаев сброс и распространение объектов работают одинаково, ключевое отличие состоит в том, что распространение определяет свойства, а Object.assign() устанавливает их. Это означает, что Object.assign() запускает сеттеры.
Стоит помнить, что в остальном объект rest/spread 1:1 отображается на Object.assign() и действует иначе, чем массив (повторяемый). Например, при распространении массива растягиваются нулевые значения. Однако использование нулевых значений распространения объекта молча распространяется на ничто.
Пример массива (Iterable) Spread
const x = [1, 2, null , 3];
const y = [...x, 4, 5];
console.log(y); // [1, 2, null, 3, 4, 5];
Пример распространения объекта
const x = null;
const y = {a: 1, b: 2};
const z = {...x, ...y};
console.log(z); //{a: 1, b: 2}
Это согласуется с тем, как Object.assign() будет работать, и оба молча исключают нулевое значение без ошибок.
const x = null;
const y = {a: 1, b: 2};
const z = Object.assign({}, x, y);
console.log(z); //{a: 1, b: 2}
Я думаю, что одно большое различие между оператором спреда и Object.assign
Похоже, что в текущих ответах не упоминается, что оператор распространения не будет сохранять прототип без изменений. Если вы хотите добавить свойства к объекту и не хотите изменять то, чем он является, то вам придется использовать Object.assign
, Пример ниже должен продемонстрировать это:
const error = new Error();
console.error instanceof Error; // true
const errorExtendedUsingSpread = {
...error,
...{
someValue: true
}
};
errorExtendedUsingSpread instanceof Error; // false
const errorExtendedUsingAssign = Object.assign(error, {
someValue: true
});
errorExtendedUsingAssign instanceof Error; // true
Между ними огромная разница с очень серьезными последствиями. Вопросы, получившие наибольшее количество голосов, даже не касаются этого, а информация о том, что разброс объектов является предложением, уже не актуальна в 2022 году.
Разница в том, что
Object.assign
изменяет объект на месте, а оператор распространения (
...
) создает совершенно новый объект, и это нарушит равенство ссылок на объекты.
Сначала давайте посмотрим на эффект, а потом я приведу реальный пример того, как важно понимать эту фундаментальную разницу.
Сначала воспользуемся Object.assign :
// Let's create a new object, that contains a child object;
const parentObject = { childObject: { hello: 'world '} };
// Let's get a reference to the child object;
const childObject = parentObject.childObject;
// Let's change the child object using Object.assign, adding a new `foo` key with `bar` value;
Object.assign(parentObject.childObject, { foo: 'bar' });
// childObject is still the same object in memory, it was changed IN PLACE.
parentObject.childObject === childObject
// true
Теперь то же упражнение с оператором распространения:
// Let's create a new object, that contains a child object;
const parentObject = { childObject: { hello: 'world '} };
// Let's get a reference to the child object;
const childObject = parentObject.childObject;
// Let's change the child object using the spread operator;
parentObject.childObject = {
...parentObject.childObject,
foo: 'bar',
}
// They are not the same object in memory anymore!
parentObject.childObject === childObject;
// false
Легко увидеть, что происходит, потому что на
parentObject.childObject = {...}
мы четко присваиваем значение ключа в
parentObject
к совершенно новому объектному литералу , и тот факт, что он составлен старым
childObject
содержание не имеет значения. Это новый объект.
И если вы полагаете, что на практике это не имеет значения, позвольте мне показать реальный сценарий того, насколько важно это понимать.
В очень большом приложении Vue.js мы начали замечать большую медлительность при вводе имени клиента в поле ввода.
После долгой отладки мы обнаружили, что каждый символ, введенный в этом вводе, запускал кучу дыр.
computed
свойства для переоценки.
Этого не ожидалось, поскольку имя заказчика вообще не использовалось в этих вычисляемых функциях. Использовались только другие данные о клиентах (например, возраст, пол). Что происходит? Почему vue переоценивал все эти вычисляемые функции, когда имя клиента менялось?
Ну, у нас был магазин Vuex, который делал это:
mutations: {
setCustomer(state, payload) {
state.customer = { ...state.customer, ...payload };
}
И наши вычисления были такими:
veryExpensiveComputed() {
const customerAge = this.$store.state.customer.age;
}
Итак, вуаля! Когда имя клиента изменилось, мутация Vuex фактически полностью изменила его на новый объект; и поскольку вычисляемый объект полагался на этот объект для получения возраста клиента, Vue рассчитывал на этот очень конкретный экземпляр объекта как на зависимость, и когда он был изменен на новый объект (сбой
===
тест на равенство объектов), Vue решил, что пришло время перезапустить вычисляемую функцию.
Исправление? Используйте Object.assign, чтобы не отбрасывать предыдущий объект, а менять его на месте...
mutations: {
setCustomer(state, payload) {
Object.assign(state.customer, payload);
}
Кстати, если вы работаете в Vue2, вам не следует использовать Object.assign, потому что Vue2 не может напрямую отслеживать изменения этих объектов, но применяется та же логика, просто используя Vue.set вместо Object.assign:
mutations: {
setCustomer(state, payload) {
Object.keys(payload).forEach(key => {
Vue.set(state.customer, key, payload[key])
})
}
ПРИМЕЧАНИЕ: распространение не просто синтаксический сахар вокруг Object.assign. Они работают по-разному за кадром.
Object.assign применяет сеттеры к новому объекту, а Spread - нет. Кроме того, объект должен быть итеративным.
Копировать Используйте это, если вам нужно, чтобы значение объекта было таким, как оно есть в данный момент, и вы не хотите, чтобы это значение отражало какие-либо изменения, сделанные другими владельцами объекта.
Используйте его для создания мелкой копии объекта. Рекомендуется всегда задавать неизменяемые свойства для копирования - поскольку изменяемые версии можно передавать в неизменяемые свойства, функция копирования гарантирует, что вы всегда будете иметь дело с неизменным объектом.
Назначить Назначить несколько противоположно копировать. Assign сгенерирует установщик, который присваивает значение переменной экземпляра напрямую, а не копирует или сохраняет его. При вызове метода получения назначенного свойства он возвращает ссылку на фактические данные.
Я хотел бы обобщить состояние функции ES "слияние распространенных объектов" в браузерах и в экосистеме с помощью инструментов.
спекуляция
- https://github.com/tc39/proposal-object-rest-spread
- Эта языковая функция является предложением этапа 4, что означает, что она была включена в спецификацию языка ES, но еще не получила широкого применения.
Браузеры: в Chrome, в SF, Firefox скоро (версия 60, IIUC)
- Поддержка браузером "распространяемых свойств", поставляемых в Chrome 60, включая этот сценарий.
- Поддержка этого сценария НЕ работает в текущем Firefox (59), но работает в моем Firefox Developer Edition. Поэтому я считаю, что он будет выпущен в Firefox 60.
- Safari: не тестировался, но Kangax говорит, что он работает в Desktop Safari 11.1, но не SF 11
- iOS Safari: не teseted, но Kangax говорит, что он работает в iOS 11.3, но не в iOS 11
- еще не в краю
Инструменты: Узел 8.7, TS 2.1
- NodeJS поддерживается с 8.7 (через Kangax). Подтверждено на 9.8, когда я тестировал локально.
- TypeScript поддерживает его с версии 2.1, текущий - 2.8
связи
Пример кода (удваивается как тест на совместимость)
var x = { a: 1, b: 2 };
var y = { c: 3, d: 4, a: 5 };
var z = {...x, ...y};
console.log(z); // { a: 5, b: 2, c: 3, d: 4 }
Опять же: на момент написания этого примера этот пример работал без перехода в Chrome (60+), Firefox Developer Edition (предварительный просмотр Firefox 60) и Node (8.7+).
Зачем отвечать?
Я пишу это через 2,5 года после первоначального вопроса. Но у меня был тот же самый вопрос, и это то, куда Google послал меня. Я раб миссии SO по улучшению длинного хвоста.
Поскольку это расширение синтаксиса "разброса по массивам", мне было очень трудно его найти в Google, и его трудно найти в таблицах совместимости. Самое близкое, что я могу найти - это Kangax "свойство-разброс", но в этом тесте нет двух спредов в одном выражении (не слияние). Кроме того, имя на страницах предложений / проектов / браузера использует слово "распространение свойств", но мне кажется, что это был "первый принцип", к которому пришло сообщество после предложений использовать синтаксис распространения для "слияния объектов". (Это может объяснить, почему в Google так сложно.) Поэтому я документирую свои находки здесь, чтобы другие могли просматривать, обновлять и компилировать ссылки об этой конкретной функции. Я надеюсь, что это завоевывает популярность. Пожалуйста, помогите распространять новости о его посадке в спецификации и в браузерах.
Наконец, я бы добавил эту информацию в качестве комментария, но я не мог редактировать их, не нарушив первоначальные намерения авторов. В частности, я не могу редактировать комментарий @ChillyPenguin без потери намерения исправить @RichardSchulte. Но спустя годы Ричард оказался прав (на мой взгляд). Поэтому я пишу этот ответ вместо этого, надеясь, что со временем он получит поддержку старых ответов (может потребоваться годы, но в конце концов это и есть эффект длинного хвоста).
Оператор распространения объекта (...) не работает в браузерах, потому что он пока не является частью какой-либо спецификации ES, а является лишь предложением. Единственный вариант - это скомпилировать его с помощью Babel (или чего-то подобного).
Как видите, это всего лишь синтаксический сахар над Object.assign({}).
Насколько я вижу, это важные различия.
- Object.assign работает в большинстве браузеров (без компиляции)
...
для объектов не нормируется...
защищает вас от случайного изменения объекта...
будет заполнять Object.assign в браузерах без него...
нужно меньше кода, чтобы выразить ту же идею
Как уже упоминали другие, в этот момент написания Object.assign()
требует полифилла и распространения объекта ...
для работы требуется некоторая прозрачность (и, возможно, также многозаполнение).
Рассмотрим этот код:
// Babel wont touch this really, it will simply fail if Object.assign() is not supported in browser.
const objAss = { message: 'Hello you!' };
const newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);
// Babel will transpile with use to a helper function that first attempts to use Object.assign() and then falls back.
const objSpread = { message: 'Hello you!' };
const newObjSpread = {...objSpread, dev: true };
console.log(newObjSpread);
Оба они дают одинаковый результат.
Вот вывод из Babel в ES5:
var objAss = { message: 'Hello you!' };
var newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var objSpread = { message: 'Hello you!' };
var newObjSpread = _extends({}, objSpread, { dev: true });
console.log(newObjSpread);
Это мое понимание до сих пор. Object.assign()
на самом деле стандартизирован, где, как объект распространения ...
еще нет. Единственная проблема - поддержка браузера для первого и в будущем, второго тоже.
Надеюсь это поможет.
Слишком много неправильных ответов...
Я сделал поиск и нашел много дезинформации по этому поводу.
Краткое содержание
- Ни то, ни другое не быстрее. Это зависит.
- почти всегда быстрее, если не учитывать побочные эффекты/мутацию объекта.
- Помимо производительности, как правило, нет никаких преимуществ или недостатков в использовании, пока вы не достигнете крайних случаев, таких как копирование объектов, содержащих геттеры/сеттеры, или свойства только для чтения. Подробнее читайте здесь.
Производительность
Будь или...spread
быстрее, это зависит от того, что вы пытаетесь объединить, и используемой среды выполнения (время от времени происходят реализации и оптимизации). Для мелких объектов это не имеет значения.
Для больших объектов обычно лучше. Особенно, если вам не нужно заботиться о побочных эффектах, выигрыш в скорости достигается за счет экономии времени за счет простого добавления свойств к первому объекту, а не копирования из двух и создания совершенно нового. Видеть:
async function retrieveAndCombine() {
const bigPayload = await retrieveData()
const smallPayload = await retrieveData2()
// only the properties of smallPayload is iterated through
// whereas bigPayload is mutated.
return Object.assign(bigPayload, smallPayload)
}
Если побочные эффекты беспокоят
В случаях, когда побочные эффекты имеют значение, например, если объект, который нужно объединить с другим, передается в качестве аргумента.
В приведенном ниже примере будут видоизменены, что является плохой идеей, если другие функции/объекты за пределамиretrieveAndCombine
зависит отbigPayload
. В этом случае вы можете поменять местами аргументы, переданные вObject.assign
, или просто используйте{}
в качестве первого аргумента для создания нового объекта:
async function retrieveAndCombine(bigPayload) {
const smallPayload = await retrieveData2()
// this is slightly more efficient
return Object.assign(smallPayload, bigPayload)
// this is still okay assuming `smallPayload` only has a few properties
return Object.assign({}, smallPayload, bigPayload)
}
Тест
Убедитесь сами, попробуйте фрагмент кода ниже. Примечание. Для запуска требуется некоторое время.
Способы (1) создания мелких копий объектов и (2) объединения нескольких объектов в один объект сильно изменились в период с 2014 по 2018 год.
Изложенные ниже подходы стали доступны и широко использовались в разное время. Этот ответ дает некоторую историческую перспективу и не является исчерпывающим.
Без какой-либо помощи библиотек или современного синтаксиса вы бы использовали
for-in
петли, напримерvar mergedOptions = {} for (var key in defaultOptions) { mergedOptions[key] = defaultOptions[key] } for (var key in options) { mergedOptions[key] = options[key] } options = mergedOptions
2006 г.
jQuery 1.0 имеет
jQuery.extend()
:options = $.extend({}, defaultOptions, options)
⋮
2010 г.
Underscore.js 1.0 имеет
_.extend()
options = _.extend({}, defaultOptions, options)
⋮
2014 г.
2ality опубликовали статью о
Object.assign()
приходит на ES2015object-assign
опубликовано в npm.var objectAssign = require('object-assign') options = objectAssign({}, defaultOptions, options)
Синтаксис Object Rest / Spread Properties, предложенный для ES2016.
2015 г.
Object.assign
поддерживается Chrome (45), Firefox (34) и Node.js (4) . Однако для более старых сред выполнения требуется Polyfill.options = Object.assign({}, defaultOptions, options)
Предложение Object Rest / Spread Properties достигает стадии 2.
2016 г.
- Синтаксис Object Rest / Spread Properties не был включен в ES2016, но предложение достигает стадии 3.
2017 г.
Синтаксис Object Rest / Spread Properties не был включен в ES2017, но его можно использовать в Chrome (60), Firefox (55) и Node.js (8.3) . Однако для более старых сред выполнения требуется некоторая транспиляция.
options = { ...defaultOptions, ...options }
2018 г.
- Предложение Object Rest / Spread Properties достигает стадии 4, и его синтаксис включен в стандарт ES2018.
Другие ответы старые, не может получить хороший ответ.
Ниже приведен пример для литералов объекта, который помогает понять, как они могут дополнять друг друга, и как они не могут дополнять друг друга (следовательно, разница):
var obj1 = { a: 1, b: { b1: 1, b2: 'b2value', b3: 'b3value' } };
// overwrite parts of b key
var obj2 = {
b: {
...obj1.b,
b1: 2
}
};
var res2 = Object.assign({}, obj1, obj2); // b2,b3 keys still exist
document.write('res2: ', JSON.stringify (res2), '<br>');
// Output:
// res2: {"a":1,"b":{"b1":2,"b2":"b2value","b3":"b3value"}} // NOTE: b2,b3 still exists
// overwrite whole of b key
var obj3 = {
b: {
b1: 2
}
};
var res3 = Object.assign({}, obj1, obj3); // b2,b3 keys are lost
document.write('res3: ', JSON.stringify (res3), '<br>');
// Output:
// res3: {"a":1,"b":{"b1":2}} // NOTE: b2,b3 values are lost
Еще несколько небольших примеров, также для массива и объекта:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
необходим, когда целевой объект является константой, и вы хотите установить несколько свойств одновременно.
Например:
const target = { data: "Test", loading: true }
Теперь предположим, что вам нужно изменить цель со всеми свойствами из источника:
const source = { data: null, loading: false, ...etc }
Object.assign(target, source) // Now target is updated
target = { ...target, ...source) // Error: cant assign to constant
Имейте в виду, что вы изменяете целевой объект, поэтому, когда это возможно, используйте
Object.assign
с пустой целью или разворотом для назначения новому объекту.
Я хотел бы добавить этот простой пример, когда вам нужно использовать Object.assign.
class SomeClass {
constructor() {
this.someValue = 'some value';
}
someMethod() {
console.log('some action');
}
}
const objectAssign = Object.assign(new SomeClass(), {});
objectAssign.someValue; // ok
objectAssign.someMethod(); // ok
const spread = {...new SomeClass()};
spread.someValue; // ok
spread.someMethod(); // there is no methods of SomeClass!
Это может быть непонятно при использовании JavaScript. Но с TypeScript проще, если вы хотите создать экземпляр некоторого класса
const spread: SomeClass = {...new SomeClass()} // Error
Теперь это часть ES6, поэтому оно стандартизировано и задокументировано в MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
Это очень удобно для использования и имеет большой смысл наряду с деструктуризацией объектов.
Единственным оставшимся преимуществом, перечисленным выше, являются динамические возможности Object.assign(), однако это так же просто, как распространение массива внутри литерального объекта. В скомпилированном выводе babel он использует именно то, что продемонстрировано с Object.assign ()
Поэтому правильным ответом будет использование разброса объектов, поскольку теперь он стандартизирован, широко используется (см. "Реакция", "Редукс" и т. Д.), Прост в использовании и обладает всеми функциями Object.assign ().
Оператор распространения разбивает массив на отдельные аргументы функции.
let iterableObjB = [1,2,3,4]
function (...iterableObjB) //turned into
function (1,2,3,4)
Мы создадим функцию с именем identity, которая просто возвращает любой параметр, который мы ей даем.
identity = (arg) => arg
И простой массив.
arr = [1, 2, 3]
Если вы позвоните в личность с помощью arr, мы знаем, что произойдет