Comments 42
И про то, в каких случаях какой способ передачи надо использовать?
const person = {
name: 'John',
age: 28
}
const newPerson = person
newPerson.age = 30
Достаточно просто
person.age = 30;
Также и с массивом
const characters = [ 'Obi-Wan', 'Vader' ]
const newCharacters = characters
newCharacters.push('Luke')
с таким же успехом можно просто
characters.push('Luke');
В js переменные это не значения, только указатели. И даже на числа и строки. Просто так уж вышло, что как и в Lua, в js просто нет функций для изменения строк и чисел по указателю — все функции чистые и возвращают новую строку\число. Но поверьте, переменная со строкой все равно есть указатель на нее.
Я конечно капитан, но статья капитан не меньше.
const не позволит перезаписать person. (+ мелкие бонусы в виде отсутствия hoisting и нормальной работы с block scopes)
const person = {age: 26}
person = {age: 30} // ошибка
В примере используется потому что просто привычка, после const/let var кажется отвратительным (ну он такой и есть)
И да, если внутри объекта есть ссылка на другой объект, то она копируется поверхностно, надо это учитывать и использовать deep clone или деструктуризацию и последующую структуризацию объектов, что кстати описано в указанной книге Кайла Симпсона на стр.62. Так что книга дельная
Никакие 'object assign', 'object spread', 'object freeze' итп не помогут в таких условиях.
Только рекурсивное клонирование, вот пример
https://github.com/timCF/jsfunky/blob/8d73af422c4e3c30f78a8e1689d2f09b7d5ccbb0/jsfunky.iced#L10-L21
Написал для упрощения жизни какое-то время назад этот модуль, пользуюсь часто в реальных задачах.
Одна из наиболее сложных операций в JavaScript – это отслеживание изменений объекта. Решения вроде Object.observe(object, callback) довольно тяжеловесны. Однако, если держать состояние неизменяемым, то можно обойтись oldObject === newObject и таким образом проверять, не изменился ли объект. Такая операция не так сильно нагружает CPU.
Вообще, даже для равных objObject и newObject они не равны, ведь сравниваются по ссылке:
var a = {value: 1};
var b = a;
console.log(a === b); // true
var a = {value: 1};
var b = {value: 2};
console.log(a === b); // false :-(
PS. Отсутствие запятых — зло.
Мне кажется иммутабельные объекты делать лучше как-то так:
class Copiable {
copy(key,value){
let result = Object.assign({},this);
result[key]=value;
result.__proto__ = this.__proto__
Object.freeze(result);
return result;
}
}
class Person extends Copiable {
constructor(name='Adam', age=31){
super();
this.name=name;
this.age=age;
Object.freeze(this);
}
}
То есть два простых правила — наследоваться от Copiable и в конце конструктора фризить объект.
const person = {
name: 'John',
password: '123',
age: 28
}
const newPerson = Object.keys(person).reduce((obj, key) => {
if (key !== property) {
return { ...obj, [key]: person[key] }
}
return obj
}, {})
А это вообще нормально N-1 раз копировать объект, добавляя по одному свойству за итерацию?
Не вижу ничего фатального. Прошлые версии будут почти моментально собраны GC. Но если что — можно добавить метод для добавления пачки полей.
Вы, верно, троллите, если пишете, что не видите проблемы насилования устройства аллокациями в цикле
const newPerson1 = {...person};
delete newPerson1[property];
// vs
const newPerson2 = Object.keys(person).reduce((obj, key) => {
if (key !== property) {
return { ...obj, [key]: person[key] }
}
return obj
}, {})
Сложно сказать. Попробовал вот на коленке пример собрать:
const keys = []; for(let i = 0; i < 100000; i += 10) keys.push(i);
console.time('mutable');
const obj1 = {};
for(let key of keys) obj1[key] = key;
console.timeEnd('mutable');
console.time('reduce');
const obj2 = keys.reduce((o, key) => Object.assign({}, o, { [key]: key }), {});
console.timeEnd('reduce');
Результат: 7.7ms vs 18'781ms. Таки JS не Haskell.
Касательно "не забывать freeze вызывать", не знаю куда именно его воткнуть. Покажите на примере выше, плз. Может быть это улучшит ситуацию.
А вот промежуточный вариант с delete
срабатывает за тоже самое время, что и чисто mutable-вариант. Т.е. в 2600 раз быстрее.
Конечно, с чего бы это оптимизатору js понимать что за ужс натворил писатель этого творения.
Object.freeze тоже совсем не обязательная вещь — глубокая заморозка очень дорогая вещь, она полезна на стадии разработки, но в продакшене мы даже не должны пытаться ошибочно изменять иммутабельный объект, или же мы совершим ошибку, за которую нас справедливо покарают исключением TypeError (в строгом режиме).
Т.е. моё мнение, что с Object.freeze хорошо тестировать код и находить ошибочные попытки изменить объект, но плохо костылить "иммутабельность" в продакшене
Мне кажется, что пока внятной поддержки имутабельных структур в js не появится ― не нужно уродоваться со всякими freeze
, и уж тем более с такой дичью, как этот reduce + {..., [key]: }
. Это уже ни в какие ворота не лезет :) Нужно искать компромиссы, вроде вышеописанного delete
.
Вот тут согласен. Как решили что функциональность работает — можно выпилить freeze.
Не-не, оно гарантированно ничего не ускорит. Более того, копирование объектов очевидно не может быть быстрее. Оно может быть только надёжнее в плане иммутабельности.
Просто по большому счёту за кадром может когда-нибудь появиться оптимизация, которая выследит, что объект можно мутировать, вместо создания копии, посмотрев на кол-во ссылок на него. Полагаю такие вещи на полном ходу работают в настоящих функциональных языках, вроде того же erlang-а.
Про что-то подобное в JS я ещё нигде не натыкался.
Ну в спеке на хаскел открытым текстом сказано что новые инстансы создаются постоянно и на месте же собираются GC если на старые версии больше никто не ссылается. Но за счёт этого immutability gc работает очень эффективно.
Полагаю, что там всё не так просто, как вы пишете. Выделять память и тут же её освобождать копируя большие куски данных на каждый чих ― это даже в теории не может быть сопоставимым, с примитивной мутацией. Разница на порядки. Полагаю, что в Haskell-е за счёт его нативной немутабельности просто много хитростей. А в JS про них пока ни намёка. Оттого и разница в 2600 раз.
Присоединяюсь к вопросу. Может кто проводил полноценные замеры? В том числе и по .concat
в []
. На первый взгляд такие вещи использовать при итерации просто дико. Но может быть там есть под капотом оптимизации под это?
Однако, на самом деле в EcmaScript есть специальный синтаксис, еще сильнее упрощающий такие задачи. Он называется object spread, использовать его можно при помощи транспилятора Babel.
А можно ссылку в спецификации про object spread? Потому что ни в последнем Chrome, ни в FF, ни в node.js 6/7 "из коробки" эта возможность не работает. Однако по таблице поддержки из статьи spread везде поддерживается полностью. Полагаю, что это какая-то драфтовая возможность
let person = {};
Object.defineProperty(person, 'name', {
value: 'John',
writable: false
});
Думаю как-то так можно сделать «иммутабельность». Так же можно попробовать freeze
Неизменяемый JavaScript: как это делается с ES6 и выше