Издательский дом «Питер» corporate blog
JavaScript
Programming
Comments 42
0
Это что, целая книга про разные способы передачи параметров в функцию: передача по ссылке (функция получает ссылку на значение аргумента и может его менять) и передача по значению (функция получает копию аргумента и не может его изменить)?
И про то, в каких случаях какой способ передачи надо использовать?
+2
Именно. Дожили называется, Хипстеры вдруг решили, что использование ссылочных типов зло, а передача по значению единственный верный вариант… :D И назвали то как пафосно «Иммутабельность» :D
0
Но… Бывает же передача иммутабельного аргумента по ссылке? Не скажу за JS, но в том же C++ вполне (а думаю, что еще в C#/Java/Scala)
+2
Далеко не специалист в JS, однако мне не понятен смысл примеров с объектами и массивами.
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');
+1
Так суть в обратном. В следующем коде

const person = {
  name: 'John',
  age: 28
}
const newPerson = person
newPerson.age = 30


нашей задачей было получить новый объект newPerson, такой же самый как person только с age 30. А проблема возникает в том что возраст изменялся и у первого объекта. В этом то и суть проблемы, о которой весь пост.
0
Ну так получайте новый объект, а не приравнивайте указатель к старому :)
В js переменные это не значения, только указатели. И даже на числа и строки. Просто так уж вышло, что как и в Lua, в js просто нет функций для изменения строк и чисел по указателю — все функции чистые и возвращают новую строку\число. Но поверьте, переменная со строкой все равно есть указатель на нее.
Я конечно капитан, но статья капитан не меньше.
0
Еще и статью не читал. Автор жалуется на то, что при присвоении происходит копирование ссылки, а не самого объекта.
+1
Я не автор, но уверен что const — просто так, и с таким же успехом его можно заменить на var. Просто в es6 принято всё что не будет изменяемо — объявлять с const.
+1

const не позволит перезаписать person. (+ мелкие бонусы в виде отсутствия hoisting и нормальной работы с block scopes)


const person = {age: 26}
person = {age: 30} // ошибка


В примере используется потому что просто привычка, после const/let var кажется отвратительным (ну он такой и есть)

0
уверен что const — просто так
В примере используется потому что просто привычка

vlreshet, Fen1kz, ок, теперь ясно.
+3
Костыли для тех, кто не понимает разницы между ссылкой и значением? Да, бывают условия, при которых нужно копирование объектов, но использовать это всегда и везде — это как-то странно. И да, ваш «object spread» сломается, как только внутри объекта появится ссылка на другой объект, поэтому без отдельной функции для «deep clone» все равно не обойтись.
0
В статье описывается функциональный подход к программированию JS. Можно использовать для Redux, когда требуется обеспечить иммутабельность данных в хранилище данных store, именно такой код используется для редюсеров (чистых функции которые принимают хранилище данных store и возвращают новое измененное хранилище).

И да, если внутри объекта есть ссылка на другой объект, то она копируется поверхностно, надо это учитывать и использовать deep clone или деструктуризацию и последующую структуризацию объектов, что кстати описано в указанной книге Кайла Симпсона на стр.62. Так что книга дельная
+1

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

0
В реальных скриптах объекты более сложные и вложенные.
Никакие 'object assign', 'object spread', 'object freeze' итп не помогут в таких условиях.
Только рекурсивное клонирование, вот пример

https://github.com/timCF/jsfunky/blob/8d73af422c4e3c30f78a8e1689d2f09b7d5ccbb0/jsfunky.iced#L10-L21

Написал для упрощения жизни какое-то время назад этот модуль, пользуюсь часто в реальных задачах.
+1
Хотя это перевод, все равно скажу.

Одна из наиболее сложных операций в 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. Отсутствие запятых — зло.
0

В тексте немного странно написано.
В редуксе смысл в том, что проверка наличия изменений сводится к newState !== oldState.
Поэтому так важно возвращать новое состояние при изменениях, а не изменять текущее

0
Ну вообще в Immutable.js реализованы persistence hash trees, так же как и в clojure. Так что в целях производительности лучше использовать библиотеку все таки
+1

Мне кажется иммутабельные объекты делать лучше как-то так:


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 и в конце конструктора фризить объект.

0
Лучше всего клонирование делать через конструкторы. То есть в идеале конструктор объекта должен принимать объект такого же типа и возвращать копию.
+1
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 раз копировать объект, добавляя по одному свойству за итерацию?

0

Не вижу ничего фатального. Прошлые версии будут почти моментально собраны GC. Но если что — можно добавить метод для добавления пачки полей.

+1

Вы, верно, троллите, если пишете, что не видите проблемы насилования устройства аллокациями в цикле


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
}, {})
0

Ну весь хаскель так работает, но ещё раз — можно оптимизировать достаточно сильно. Главное н езаьывать фриз сделать.

0

Сложно сказать. Попробовал вот на коленке пример собрать:


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 вызывать", не знаю куда именно его воткнуть. Покажите на примере выше, плз. Может быть это улучшит ситуацию.

0

А вот промежуточный вариант с delete срабатывает за тоже самое время, что и чисто mutable-вариант. Т.е. в 2600 раз быстрее.

+1

Конечно, с чего бы это оптимизатору js понимать что за ужс натворил писатель этого творения.


Object.freeze тоже совсем не обязательная вещь — глубокая заморозка очень дорогая вещь, она полезна на стадии разработки, но в продакшене мы даже не должны пытаться ошибочно изменять иммутабельный объект, или же мы совершим ошибку, за которую нас справедливо покарают исключением TypeError (в строгом режиме).


Т.е. моё мнение, что с Object.freeze хорошо тестировать код и находить ошибочные попытки изменить объект, но плохо костылить "иммутабельность" в продакшене

0

Мне кажется, что пока внятной поддержки имутабельных структур в js не появится ― не нужно уродоваться со всякими freeze, и уж тем более с такой дичью, как этот reduce + {..., [key]: }. Это уже ни в какие ворота не лезет :) Нужно искать компромиссы, вроде вышеописанного delete.

0

Вот тут согласен. Как решили что функциональность работает — можно выпилить freeze.

0

Не-не, оно гарантированно ничего не ускорит. Более того, копирование объектов очевидно не может быть быстрее. Оно может быть только надёжнее в плане иммутабельности.

0

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


Про что-то подобное в JS я ещё нигде не натыкался.

0

Ну в спеке на хаскел открытым текстом сказано что новые инстансы создаются постоянно и на месте же собираются GC если на старые версии больше никто не ссылается. Но за счёт этого immutability gc работает очень эффективно.

0

Полагаю, что там всё не так просто, как вы пишете. Выделять память и тут же её освобождать копируя большие куски данных на каждый чих ― это даже в теории не может быть сопоставимым, с примитивной мутацией. Разница на порядки. Полагаю, что в Haskell-е за счёт его нативной немутабельности просто много хитростей. А в JS про них пока ни намёка. Оттого и разница в 2600 раз.

+1

А я уже и забыл про ту статью. Спасибо, что напомнили. Правда это такой "хак", что всем хакам хак. Не думаю, что стоит полагаться на такое в production-е.

+1

Согласен. Насчет примера выше, имеет ли смысл каждый раз создавать объект? Mожет лучше сразу:


const obj2 = keys.reduce((o, key) => 
  Object.assign(o, { [key]: key }), {});
0

Присоединяюсь к вопросу. Может кто проводил полноценные замеры? В том числе и по .concat в []. На первый взгляд такие вещи использовать при итерации просто дико. Но может быть там есть под капотом оптимизации под это?

0
Однако, на самом деле в EcmaScript есть специальный синтаксис, еще сильнее упрощающий такие задачи. Он называется object spread, использовать его можно при помощи транспилятора Babel.

А можно ссылку в спецификации про object spread? Потому что ни в последнем Chrome, ни в FF, ни в node.js 6/7 "из коробки" эта возможность не работает. Однако по таблице поддержки из статьи spread везде поддерживается полностью. Полагаю, что это какая-то драфтовая возможность

0
let person = {};
Object.defineProperty(person, 'name', {
  value: 'John',
  writable: false
});

Думаю как-то так можно сделать «иммутабельность». Так же можно попробовать freeze
Only those users with full accounts are able to leave comments., please.