Чому незмінність так важлива (або потрібна) в JavaScript?


205

Зараз я працюю над рамками React JS та React Native . На півдорозі я натрапив на Immutability або Immutable-JS Library , коли я читав про реалізацію Flux та Redux у Facebook.

Питання в тому, чому незмінність так важлива? Що не так у мутуванні предметів? Чи не робить це просто?

Надаючи приклад, розглянемо просте додаток для читання новин, на екрані, що відкривається, є переліком заголовків новин.

Якщо я встановив, скажімо, масив об'єктів зі значенням спочатку я не можу ним маніпулювати. Ось що говорить принцип незмінності, правда? (Виправте мене, якщо я помиляюся.) Але що робити, якщо у мене є новий об’єкт Новини, який потрібно оновити? У звичайному випадку я міг просто додати об’єкт до масиву. Як я досягти в цьому випадку? Видалити магазин і відтворити його? Чи не додавання об’єкта до масиву є менш дорогою операцією?



2
Незмінна структура даних та чиста функція призводять до прозорості референсу, що робить його набагато простішим міркуванням про поведінку вашої програми. Ви також отримуєте зворотний трек безкоштовно, використовуючи функціональну структуру даних.
WorBlux

Я надав точку зору Redux @bozzmob.
prosti

1
Може бути корисним дізнатися про незмінність взагалі як про концепцію функціональної парадигми, а не намагатися думати, що JS має певне відношення до цього. Реакцію пишуть шанувальники функціонального програмування. Ви повинні знати, що вони знають, щоб їх зрозуміти.
Герман

Відповіді:


196

Я нещодавно досліджував ту саму тему. Я докладу всіх зусиль, щоб відповісти на ваші запитання і спробувати поділитися тим, що я дізнався до цього часу.

Питання в тому, чому незмінність так важлива? Що не так у мутуванні предметів? Чи не робить це просто?

В основному це зводиться до того, що незмінність підвищує передбачуваність, продуктивність (опосередковано) і дозволяє відслідковувати мутації.

Передбачуваність

Мутація приховує зміни, які створюють (несподівані) побічні ефекти, які можуть викликати неприємні помилки. Коли ви застосовуєте незмінність, ви можете зберігати архітектуру програми та ментальну модель простою, що полегшує міркування щодо вашої програми.

Продуктивність

Навіть незважаючи на те, що додавання значень незмінному Об'єкту означає, що потрібно створити новий екземпляр, де потрібно скопіювати наявні значення та додати нові значення до нового Об'єкта, який коштує пам'ять, незмінний Об'єкти можуть використовувати структурний спільний доступ для зменшення пам'яті накладні.

Усі оновлення повертають нові значення, але внутрішні структури поділяються для того, щоб різко скоротити використання пам'яті (і обмін GC). Це означає, що якщо ви додасте вектор до 1000 елементів, він фактично не створює нового вектора 1001-елементами. Швидше за все, внутрішньо виділено лише кілька невеликих об’єктів.

Більше про це можна прочитати тут .

Відстеження мутацій

Окрім зменшеного використання пам’яті, незмінність дозволяє оптимізувати додаток, використовуючи рівність еталону та значення. Це робить насправді легко зрозуміти, чи щось змінилося. Наприклад, зміна стану компонента, що реагує. Ви можете shouldComponentUpdateперевірити, чи стан однаковий, порівнюючи Об'єкти стану та запобігаючи непотрібному візуалізації. Більше про це можна прочитати тут .

Додаткові ресурси:

Якщо я встановити, скажімо, масив об'єктів зі значенням спочатку. Я не можу цим маніпулювати. Ось що говорить принцип незмінності, правда? (Виправте мене, якщо я помиляюся). Але що робити, якщо у мене є новий об’єкт Новини, який потрібно оновити? У звичайному випадку я міг просто додати об’єкт до масиву. Як я досягти в цьому випадку? Видалити магазин і відтворити його? Чи не додавання об’єкта до масиву є менш дорогою операцією?

Так, це правильно. Якщо ви плутаєтесь як реалізувати це у вашій програмі, я б рекомендував вам поглянути на те, як редукс робить це для ознайомлення з основними поняттями, це мені дуже допомогло.

Мені подобається використовувати Redux як приклад, оскільки він охоплює незмінність. Він має єдине незмінне дерево стану (згадане як store), де всі зміни стану явні шляхом диспетчерських дій, які обробляються редуктором, який приймає попередній стан разом із зазначеними діями (по одному) і повертає наступний стан вашої програми . Детальніше про основні принципи ви можете прочитати тут .

Існує чудовий курс редукцій на egghead.io, де Дан Абрамов , автор redux, пояснює ці принципи так (я трохи змінив код, щоб краще відповідати сценарію):

import React from 'react';
import ReactDOM from 'react-dom';

// Reducer.
const news = (state=[], action) => {
  switch(action.type) {
    case 'ADD_NEWS_ITEM': {
      return [ ...state, action.newsItem ];
    }
    default: {
        return state;
    }
  }
};

// Store.
const createStore = (reducer) => {
  let state;
  let listeners = [];

  const subscribe = (listener) => {
    listeners.push(listener);

    return () => {
      listeners = listeners.filter(cb => cb !== listener);
    };
  };

  const getState = () => state;

  const dispatch = (action) => {
    state = reducer(state, action);
    listeners.forEach( cb => cb() );
  };

  dispatch({});

  return { subscribe, getState, dispatch };
};

// Initialize store with reducer.
const store = createStore(news);

// Component.
const News = React.createClass({
  onAddNewsItem() {
    const { newsTitle } = this.refs;

    store.dispatch({
      type: 'ADD_NEWS_ITEM',
      newsItem: { title: newsTitle.value }
    });
  },

  render() {
    const { news } = this.props;

    return (
      <div>
        <input ref="newsTitle" />
        <button onClick={ this.onAddNewsItem }>add</button>
        <ul>
          { news.map( ({ title }) => <li>{ title }</li>) }
        </ul>
      </div>
    );
  }
});

// Handler that will execute when the store dispatches.
const render = () => {
  ReactDOM.render(
    <News news={ store.getState() } />,
    document.getElementById('news')
  );
};

// Entry point.
store.subscribe(render);
render();

Крім того, ці відео детальніше демонструють, як досягти незмінності для:


1
@naomik дякую за відгук! Мій намір полягав у тому, щоб проілюструвати концепцію та чітко показати, що Об'єкти не мутуються, а не обов'язково показувати, як її реалізувати до кінця. Однак мій приклад може бути трохи заплутаним, я його трохи оновлю.
danillouz

2
@bozzmob ласкаво просимо! Ні, що невірно, вам потрібно забезпечити незмінність у редукторі самостійно. Це означає, що ви можете дотримуватися таких стратегій, як продемонстровано у відео, або використовувати бібліотеку на зразок imutablejs. Ви можете знайти більше інформації тут і тут .
danillouz

1
@naomik ES6 const- це не про незмінність. Матіас Байненс написав про це чудову статтю в блозі .
Lea Rosema

1
@terabaud дякую за те, що поділився посиланням. Я згоден, це важлива відмінність. ^ _ ^
Дякую

4
Поясніть, будь ласка, "Мутація приховує зміни, які створюють (несподівані) побічні ефекти, які можуть спричинити неприємні помилки. Коли ви застосовуєте незмінність, ви можете зберегти архітектуру програми та ментальну модель простою, що полегшує міркування щодо вашої програми". Тому що це зовсім не так у контексті JavaScript.
Павло Лекіч

142

Контраріанський погляд на непорушність

TL / DR: Незмінюваність - це більше модна тенденція, ніж необхідність у JavaScript. Якщо ви використовуєте React, це забезпечує акуратну обробку деяких заплутаних варіантів дизайну в управлінні державою. Однак у більшості інших ситуацій він не додасть достатньої вартості через складність, яку він вводить, слугуючи більше, щоб підбити резюме, ніж задовольнити фактичну потребу клієнта.

Довга відповідь: читайте нижче.

Чому незмінність так важлива (або потрібна) у JavaScript?

Ну, я радий, що ти запитав!

Деякий час тому дуже талановитий хлопець на ім’я Дан Абрамов написав бібліотеку управління державним JavaScript під назвою Redux, яка використовує чисті функції та незмінність. Він також зробив кілька справді цікавих відео, які зробили ідею дуже зрозумілою (і продати).

Терміни були ідеальними. Новинка Angular затухала, і JavaScript у світі був готовий докласти найновішу річ, яка мала ступінь класу, і ця бібліотека була не лише новаторською, але і чудово поєднаною з React, який був проданий іншою електростанцією Silicon Valley .

Як це не сумно, у світі JavaScript владають модами. Зараз Абрамова називають напівбогом, і всі ми, просто смертні, повинні піддаватися Дао непорушності ... Це має сенс чи ні.

Що не так у мутуванні предметів?

Нічого!

Насправді програмісти мутували об’єкти для ер ... до тих пір, поки існували об'єкти для мутації. 50+ років розробки додатків, іншими словами.

І навіщо ускладнювати речі? Коли у вас є об'єкт, catі він гине, чи вам справді потрібна секунда, catщоб відстежувати зміни? Більшість людей просто сказали cat.isDead = trueб і з цим все зробили.

Чи не (мутують об’єкти) речі не прості?

ТАК! .. Звичайно, так!

Особливо в JavaScript, який на практиці найкорисніше використовується для надання уявлення про стан, який підтримується в іншому місці (наприклад, у базі даних).

Що робити, якщо у мене новий об’єкт Новини, який потрібно оновити? ... Як я досягти в цьому випадку? Видалити магазин і відтворити його? Чи не додавання об’єкта до масиву є менш дорогою операцією?

Ну, ви можете піти на традиційний підхід і оновити Newsоб’єкт, так що ваше уявлення в пам'яті цього об’єкта змінюється (і представлення, відображене користувачеві, або так можна сподіватися) ...

Або в якості альтернативи ...

Ви можете спробувати сексуальний підхід FP / Immutability і додати свої зміни до Newsоб'єкта до масиву, який відстежує кожну історичну зміну, щоб потім ви могли перебирати масив і з'ясувати, яким має бути правильне представлення стану (феу!).

Я намагаюся дізнатися, що саме тут. Будь ласка, просвіти мене :)

Моди приходять і йдуть приятелю. Існує багато способів шкіряти кота.

Мені шкода, що вам доведеться нести плутанину постійно змінюється набору парадигм програмування. Але ей, ВІТАЙТЕ ДО КЛУБУ !!

Тепер ще кілька важливих моментів, які слід пам’ятати, що стосується непорушності, і ви натрапляєте на вас з гарячковою інтенсивністю, яку може сприйняти лише наївність.

1) Незмінність є приголомшливою для того, щоб уникнути перегонів у багатопотокових середовищах .

Багатопотокові середовища (наприклад, C ++, Java та C #) винні в практиці блокування об'єктів, коли більше одного потоку хоче їх змінити. Це погано для продуктивності, але краще, ніж альтернатива корупції даних. І все ж не так добре, як зробити все непорушним (Господь хвалить Хаскелла!).

АЛЕ АЛАС! У JavaScript ви завжди працюєте на одному потоці . Навіть веб-працівників (кожен працює в окремому контексті ). Отже, оскільки у вашому контексті виконання (у всіх цих чудових глобальних змінних та закриттів) у вас не може бути умови перегонів, пов’язаних з потоком , головний момент на користь Immutability виходить у вікно.

(Сказавши це, є перевага в застосуванні чистих функцій у веб-службовців. Це те, що ви не будете сподіватися на те, що ви зможете познайомитися з об'єктами на головній темі.)

2) Незмінюваність може (якимось чином) уникнути перегонів у стані вашої програми.

І ось справжня суть справи, більшість розробників (React) скажуть вам, що Immutability та FP можуть якось працювати над цією магією, що дозволяє стану вашої програми стати передбачуваним.

Звичайно, це не означає, що ви можете уникнути перегонових умов у базі даних , щоб витягнути це, вам доведеться координувати всіх користувачів у всіх браузерах , і для цього вам знадобиться зворотна технологія push як WebSockets ( докладніше про це нижче), що транслюватиме зміни для всіх, хто працює з додатком.

Також це не означає, що в JavaScript існує якась притаманна проблема, коли стан вашої програми потребує незмінності, щоб стати передбачуваним, будь-який розробник, який кодував додаткові програми перед React, сказав би вам це.

Ця досить заплутана заява просто означає, що з React ваш додаток стане більш схильним до перегонів , але ця незмінність дозволяє зняти цей біль. Чому? Оскільки React є особливим .. він був розроблений як високооптимізована бібліотека візуалізації, когерентне управління державою займає друге місце, і таким чином стан компонентів управляється за допомогою асинхронного ланцюга подій (він же "одностороння прив'язка даних"), що у вас немає контролю і покладатися на вас, пам'ятаючи, що не мутувати стан безпосередньо ...

З огляду на цей контекст, легко зрозуміти, як потреба в незміненості мало спільного з JavaScript і багато спільного з перегоновими умовами в React: якщо у вашій програмі є маса взаємозалежних змін і немає простого способу зрозуміти, що зараз у вашому стані, ви заплутаєтесь , і, таким чином, має сенс використовувати незмінність для відстеження всіх історичних змін .

3) Умови гонки категорично погані.

Ну, вони можуть бути, якщо ви використовуєте React. Але вони рідкісні, якщо ви підбираєте іншу рамку.

Крім того, у вас зазвичай виникають набагато більші проблеми з вирішенням ... Такі проблеми, як пекло залежності. Наче роздута база коду. Наче ваш CSS не завантажується. Як і повільний процес збирання або застряг у монолітній задній частині, що робить ітерацію майже неможливою. Як і недосвідчені дияволи, які не розуміють, що відбувається, і роблять безлад.

Ти знаєш. Реальність. Але ей, кого це хвилює?

4) Незмінність використовує еталонні типи, щоб зменшити ефективність відстеження кожної зміни стану.

Тому що серйозно, якщо ви збираєтесь копіювати матеріали щоразу, коли ваш стан змінюється, краще переконайтеся, що ви розумні в цьому.

5) Незмінюваність дозволяє знімати матеріали UNDO .

Тому що е .. це функція номер один, яку запросить ваш менеджер проектів, правда?

6) Стан, що змінюється, має багато цікавого потенціалу в поєднанні з WebSockets

І останнє, але не менш важливе, накопичення дельти штатів робить досить переконливим випадком у поєднанні з WebSockets, що дозволяє легко спожити стан як потік незмінних подій ...

Як тільки копійка падає на цю концепцію (держава є потоком подій, а не сирим набором рекордів, що представляють останню думку), незмінний світ стає магічним місцем для проживання. Країна дивовижних подій і можливостей, що виходить за рамки самого часу . І коли все зроблено правильно , це може виразно зробити в режимі реального часу додаток Easi ер для виконання, ви просто транслювати потік подій для всіх бажаючих , щоб вони могли побудувати своє власне уявлення про сьогодення і записати назад свої зміни в комунальний потік.

Але в якийсь момент ти прокидається і розумієш, що все це диво і магія не приходять безкоштовно. На відміну від ваших колег, які прагнуть, ваші зацікавлені сторони (так, люди, які платять вам) мало дбають про філософію чи моду та багато грошей, які вони платять, щоб створити продукт, який вони можуть продати. І суть полягає в тому, що важче написати незмінний код і простіше його зламати, плюс мало сенсу мати непорушний фронт-енд, якщо у вас немає бек-енду для його підтримки. Коли (і якщо!) Ви нарешті переконуєте своїх зацікавлених сторін у тому, що ви повинні публікувати та споживати події за допомогою поштовхової технології, як WebSockets, ви дізнаєтесь, який біль викликає масштаб у виробництві .


Тепер для поради, якщо ви вирішите її прийняти.

Вибір написання JavaScript за допомогою FP / Immutability також є вибором для того, щоб зробити базу коду додатків більш масштабною, більш складною та важкою для управління. Я настійно заперечую за обмеження такого підходу до ваших редукторів Redux, якщо ви не знаєте, що ви робите ... І якщо ви збираєтеся йти вперед і використовувати незмінність незалежно, то застосуйте непорушний стан до всього свого пакета програм , а не лише на стороні клієнта, оскільки у вас відсутня реальна цінність.

Тепер, якщо вам пощастило, щоб можна було робити вибір у своїй роботі, то спробуйте використати свою мудрість (чи ні) і робіть те, що належить вам, хто платить вам . Ви можете це базувати на своєму досвіді, на вашій кишці або на тому, що відбувається навколо вас (правда, якщо всі користуються React / Redux, тоді є вагомий аргумент, що буде легше знайти ресурс для продовження вашої роботи). Або ж, ви можете спробувати або відновити розроблену розробку, або підхід під керуванням Hype . Вони можуть бути більше вашою справою.

Коротше кажучи, річ , щоб сказати про незмінність, що це буде зробити вас модним з вашими колегами, принаймні до наступного повальне захоплення відбувається навколо, з допомогою чого ви будете раді , що рухатися далі.


Тепер після цього сеансу самолікування хотілося б зазначити, що я додав це як статтю у своєму блозі => Незмінюваність у JavaScript: Контррианський погляд . Не соромтесь відповісти там, якщо у вас є сильні почуття, ви хотіли б також зійти з грудей;).


10
Привіт, Стівен, так. У мене були всі ці сумніви, коли я вважав незмінним.js і redux. Але, ваша відповідь дивовижна! Це додає великої цінності і подяки за звернення до кожного окремого моменту, в якому я мав сумніви. Це набагато зрозуміліше / краще зараз навіть після роботи місяцями над незмінними об'єктами.
bozzmob

5
Я використовую React з Flux / Redux вже більше двох років, і я не міг більше погодитися з вами, чудова реакція!
Павло Лекич

6
Я дуже підозрюю, що погляди на незмінність досить чітко співвідносяться з розмірами команди та кодової бази, і я не думаю, що це випадковість, що головним прихильником є ​​гігантський долинний гігант. Попри це, я з повагою не погоджуюся: незмінність - це корисна дисципліна, як не використання goto - корисна дисципліна. Або одиничне тестування. Або TDD. Або статичний аналіз типу. Це не означає, що ви робите їх постійно, кожен раз (хоча деякі так і роблять). Я б також сказав, що корисність є ортогональною для підкрутки: у матриці корисного / зайвого та сексуального / нудного є безліч прикладів кожного. "hyped" !== "bad"
Джаред Сміт

4
Привіт @ftor, хороший момент, ведучи речі занадто далеко в інший бік. Однак, оскільки там є така суттєвість "про-незмінюваності в статтях JavaScript" та аргументів, я відчув, що мені потрібно збалансувати речі. Тож у новачків є протилежна точка зору, яка допоможе їм у будь-якому випадку зробити судження.
Стівен де Салас

4
Інформативний та з блискучим титулом. Поки я не знайшов цієї відповіді, я думав, що я єдиний, хто дотримується подібного погляду. Я визнаю цінність незмінюваності, але мене турбує те, що вона стала такою загальноприйнятою догмою, яка гнітить (наприклад, на шкоду двосторонньому зв'язуванню, що шалено корисно для форматування вводу, наприклад, реалізованого в KnockoutJS).
Тиблиць

53

Питання в тому, чому незмінність так важлива? Що не так у мутуванні предметів? Чи не робить це просто?

Власне, і навпаки: змінність ускладнює справи, принаймні, в довгостроковій перспективі. Так, це полегшує ваше початкове кодування, оскільки ви можете просто змінювати речі, куди хочете, але коли ваша програма стає більшою, це стає проблемою - якщо значення змінилося, що змінило його?

Якщо ви зробите все непорушним, це означає, що дані не можна більше змінювати зненацька. Ви точно знаєте, що якщо ви передаєте значення функції, воно не може бути змінено в цій функції.

Простіше кажучи: якщо ви користуєтеся незмінними значеннями, це дуже легко міркує про ваш код: кожен отримує унікальну * копію ваших даних, тому він не може зіткнутися з ним і зламати інші частини вашого коду. Уявіть, наскільки простіше це робить роботу в багатопотоковому середовищі!

Примітка 1: Потенційна ціна на незмінність залежить від того, що ви робите, але такі речі, як Immutable.js, оптимізуються якнайкраще.

Примітка 2: У напевно, ви не були впевнені, Immutable.js та ES6 constозначають дуже різні речі.

У звичайному випадку я міг просто додати об’єкт до масиву. Як я досягти в цьому випадку? Видалити магазин і відтворити його? Чи не додавання об’єкта до масиву є менш дорогою операцією? PS: Якщо приклад не є правильним способом пояснити незмінність, будь ласка, дайте мені знати, який правильний практичний приклад.

Так, ваш приклад новин ідеально хороший, а ваші міркування абсолютно правильні: ви не можете просто внести зміни в існуючий список, тому вам потрібно створити новий:

var originalItems = Immutable.List.of(1, 2, 3);
var newItems = originalItems.push(4, 5, 6);

1
Я не погоджуюся з цією відповіддю, але вона не стосується його частини запитання "Я хотів би навчитися на практичному прикладі". Можна стверджувати, що єдине посилання на перелік заголовків новин, що використовуються в декількох областях, є хорошою справою. "Мені потрібно лише оновити список лише один раз, і все, на що посилається на список новин, оновлюється безкоштовно" - я думаю, що краща відповідь спричинить загальну проблему, як він представив, і показати цінну альтернативу, яка використовує незмінність.
Дякую

1
Я радий, що відповідь була корисною! Щодо вашого нового запитання: не намагайтеся перегадати систему :) У цьому точному випадку щось, що називається "структурний обмін", значно зменшує обертання GC - якщо у списку є 10000 елементів та додайте ще 10, я вважаю незмінними. js спробує повторно використати попередню структуру якнайкраще. Нехай Immutable.js турбується про пам’ять, і швидше за все, ви виявите, що це виходить краще.
TwoStraws

6
Imagine how much easier this makes working in a multi-threaded environment!-> Добре для інших мов, але це не є перевагою в однопотоковому JavaScript.
Стівен де Салас

1
@StevendeSalas зауважимо, що JavaScript в першу чергу асинхронний та керований подіями. Він зовсім не застрахований від перегонів.
Джаред Сміт

1
@JaredSmith все ще залишається моєю точкою. FP і Immutability - це корисні парадигми для уникнення пошкодження даних та / або блокування ресурсів у багатопотокових середовищах, але не так у JavaScript, оскільки його єдина нитка. Якщо я не пропускаю якийсь святий самородок мудрості, головна компромісна робота полягає в тому, чи готові ви зробити код більш складним (і повільнішим) у прагненні уникати перегонових умов ... що набагато менше питання, ніж більшість людей думати.
Стівен де Салас

37

Хоча інші відповіді чудові, але для вирішення вашого питання щодо практичного використання (з коментарів до інших відповідей) можна на хвилину вийти за межі вашого бігового коду і подивитися на всюдисущу відповідь прямо під носом: git . Що станеться, якщо кожен раз, коли ви натискаєте на комісію, ви перезаписуєте дані у сховищі?

Тепер ми вирішили одну з проблем, з якими стикаються незмінні колекції: роздуття пам'яті. Git досить розумний, щоб не просто робити нові копії файлів кожного разу, коли ви вносите зміни, він просто відстежує відмінності .

Хоча я не знаю багато про внутрішню роботу git, я можу лише припустити, що вона використовує аналогічну стратегію бібліотеки, на яку ви посилаєтесь: структурний обмін. Під кришкою бібліотеки використовують спроби чи інші дерева лише для відстеження різних вузлів.

Ця стратегія також є досить ефективною для структур даних в пам'яті, оскільки існують відомі алгоритми роботи з деревами, які працюють в логарифмічний час.

Ще один випадок використання: скажіть, що ви хочете кнопку скасування на веб-сайті. З незмінним уявленням ваших даних реалізація таких даних є відносно тривіальною. Але якщо ви покладаєтесь на мутацію, це означає, що вам доведеться турбуватися про кешування стану світу та проведення атомних оновлень.

Коротше кажучи, є ціна, яку потрібно заплатити за незмінність виконання часу роботи та криву навчання. Але будь-який досвідчений програміст скаже вам, що час налагодження переважає час написання коду на порядок. І незначне враження від продуктивності виконання, ймовірно, переважає помилки, пов’язані з державою, ваші користувачі не повинні терпіти.


1
Яскравий приклад, я кажу. Моє розуміння непорушності зараз більш зрозуміле. Дякую Джаред. Насправді однією з реалізацій є кнопка UNDO: D І ви зробили речі для мене досить простими.
bozzmob

3
Тільки тому, що візерунок, який має сенс у git, не означає, що однакова річ має сенс скрізь. У git ви насправді дбаєте про всю історію, що зберігається, і ви хочете мати можливість об'єднати різні гілки. На передній план ви не переймаєтесь більшою частиною історії держави, і вам не потрібна вся ця складність.
Лижі

2
@Ski це лише складно, тому що це не за замовчуванням. Зазвичай у моїх проектах не використовую mori або imtable.js: я завжди вагаюся брати участь у сторонніх розробниках. Але якби це був за замовчуванням (a la clojurescript) або, принаймні, мав натурний варіант відключення, я б використовував його весь час, тому що коли я, наприклад, програмую в clojure, я не одразу все складаю в атоми.
Джаред Сміт

Джо Армстронг сказав би, що не турбуйтеся про продуктивність, просто зачекайте кілька років, і закон Мура подбає про це за вас.
ximo

1
@JaredSmith Ти маєш рацію, все лише стає менше і обмежує ресурси. Я не впевнений, чи це буде обмежуючим фактором JavaScript. Ми постійно знаходимо нові способи підвищення продуктивності (наприклад, Svelte). До речі, я повністю згоден з вашим іншим коментарем. Складність або складність використання незмінних структур даних часто зводиться до мови, що не має вбудованої підтримки концепції. Clojure робить незмінність простою, оскільки вона випікається на мові, вся ідея була розроблена навколо ідеї.
ximo

8

Питання в тому, чому незмінність так важлива? Що не так у мутуванні предметів? Чи не робить це просто?

Про незмінність

Нічого поганого в зміні з технічної точки зору. Це швидко, це повторне використання пам'яті. Розробники використовують це з самого початку (наскільки я його пам’ятаю). Проблема існує у використанні змінності та проблем, які це використання може спричинити.

Якщо об'єкт ні з чим не поділяється, наприклад, існує в межах функції і не піддається зовнішній стороні, то важко побачити користь у незміненості. Дійсно в цьому випадку не має сенсу бути непорушним. Почуття непорушності починається, коли щось ділиться.

Мутаційний головний біль

Змінна структура спільного використання може легко створити багато підводних каменів. Будь-яка зміна будь-якої частини коду з доступом до посилання впливає на інші частини із видимістю цього посилання. Такий вплив з'єднує всі частини разом, навіть коли вони не повинні знати про різні модулі. Мутація в одній функції може призвести до краху абсолютно різної частини програми. Така річ - поганий побічний ефект.

Далі часто проблемою мутації є пошкоджений стан. Пошкоджений стан може статися, коли процедура мутації провалиться посередині, а деякі поля були змінені, а деякі - ні.

Більше того, за допомогою мутації важко відстежити зміни. Проста перевірка довідки не покаже різниці, щоб знати, що змінилося, щоб зробити глибоку перевірку. Також для моніторингу змін необхідно ввести деяку спостережувану схему.

Нарешті, мутація є причиною дефіциту довіри. Як ви можете бути впевнені, що якась структура шукає значення, якщо її можна мутувати.

const car = { brand: 'Ferrari' };
doSomething(car);
console.log(car); // { brand: 'Fiat' }

Як показано вище в прикладі, передача змінної структури завжди може закінчуватися, маючи різну структуру. Функція doSomething мутує атрибут, наданий зовні. Не довіряйте коду, ви дійсно не знаєте, що у вас є і що у вас буде. Усі ці проблеми виникають тому, що: Змінні структури представляють покажчики на пам'ять.

Незмінюваність - це значення

Незмінність означає, що зміна не робиться на одному об’єкті, структурі, але зміна представлена ​​в новій. І це тому, що посилання представляє значення не тільки вказівника пам'яті. Кожна зміна створює нове значення і не торкається старого. Такі чіткі правила повертають довіру та передбачуваність коду. Функції безпечно використовувати, оскільки замість мутації вони мають власні версії з власними значеннями.

Використання значень замість контейнерів пам'яті дає впевненість, що кожен об'єкт представляє певне незмінне значення і його безпечно використовувати.

Незмінні структури представляють значення.

Я ще більше занурююся в тему в середній статті - https://medium.com/@macsikora/the-state-of-immutability-169d2cd11310


6

Чому незмінність так важлива (або потрібна) в JavaScript?

Незмінюваність можна відстежувати в різних контекстах, але найважливішим було б відстежувати її проти стану програми та проти інтерфейсу програми.

Я буду вважати шаблон JavaScript Redux як дуже модний і сучасний підхід, і тому, що ви це згадали.

Для інтерфейсу ми повинні зробити його передбачуваним . Це буде передбачувано, якщо UI = f(application state).

Програми (в JavaScript) змінюють стан за допомогою дій, реалізованих за допомогою функції редуктора .

Функція редуктора просто виконує дію та старий стан і повертає новий стан, зберігаючи старий стан недоторканим.

new state  = r(current state, action)

введіть тут опис зображення

Перевага полягає в тому, що ви подорожуєте штатами, оскільки всі об'єкти стану збережені, і ви можете надати програму в будь-якому стані, оскільки UI = f(state)

Таким чином, ви можете легко скасувати / повторити.


Буває створення всіх цих станів, як і раніше, може бути ефективною пам'яттю, аналогія з Git є чудовою, і ми маємо подібну аналогію в ОС Linux з символічними посиланнями (засновані на індексах).


5

Ще одна перевага Immutability у Javascript полягає в тому, що він зменшує тимчасове з’єднання, що має значні переваги для дизайну взагалі. Розглянемо інтерфейс об'єкта двома методами:

class Foo {

      baz() {
          // .... 
      }

      bar() {
          // ....
      }

}

const f = new Foo();

Можливо, потрібен виклик на те baz(), щоб об'єкт перейшов у дійсний стан для правильного виклику bar(). Але як ти це знаєш?

f.baz();
f.bar(); // this is ok

f.bar();
f.baz(); // this blows up

Щоб зрозуміти це, вам потрібно ретельно вивчити внутрішні класи класу, оскільки це не відразу зрозуміло з вивчення публічного інтерфейсу. Ця проблема може вибухнути у великій кодовій базі з великою кількістю змінних станів та класів.

Якщо Fooнепорушний, то це вже не проблема. Можна впевнено припустити, що ми можемо дзвонити bazабо barв будь-якому порядку, оскільки внутрішній стан класу не може змінитися.


4

Колись виникла проблема із синхронізацією даних між потоками. Ця проблема була сильною болем, було 10+ рішень. Деякі намагалися вирішити це радикально. Це було місце, де народилося функціональне програмування. Це як марксизм. Я не міг зрозуміти, як Дан Абрамов продав цю ідею в JS, тому що вона є єдиною ниткою. Він геній.

Я можу навести невеликий приклад. У __attribute__((pure))gcc є атрибут . Компілятори намагаються вирішити, чи ваша функція чиста чи ні, якщо ви її не декларуєте спеціально. Ваша функція може бути чистою, навіть ваш стан є змінним. Незмінюваність - це лише один із 100+ способів гарантувати, що функція буде чистою. Насправді 95% ваших функцій будуть чистими.

Не слід використовувати будь-які обмеження (наприклад, незмінність), якщо ви насправді не маєте серйозних причин. Якщо ви хочете "скасувати" деякий стан, ви можете створювати транзакції. Якщо ви хочете спростити спілкування, ви можете надсилати події з незмінними даними. Тобі вирішувати.

Я пишу це повідомлення з республіки постмарксизму. Я впевнений, що радикалізація будь-якої ідеї - це неправильний шлях.


3-й абзац має стільки сенсу. Дякую тобі за це. "Якщо ви хочете" скасувати "деякий стан, ви можете створити транзакції" !!
bozzmob

Порівняння з марксизмом, до речі, можна зробити і для ООП. Пам'ятаєте Java? Чорт, дивні біти Java в JavaScript? Хайп ніколи не є добрим, він викликає радикалізацію та поляризацію. Історично OOP був набагато більш розчуленим, ніж Facebook, який мав бажання Redux. Хоча вони впевнені, намагалися все можливе.
ximo

4

Різне сприйняття ...

Інша моя відповідь стосується питання з дуже практичної точки зору, і мені це все одно подобається. Я вирішив додати це як іншу відповідь, а не як доповнення до цієї, тому що це нудна філософська розпуста, яка, сподіваємось, також відповідає на питання, але насправді не відповідає моїй існуючій відповіді.

TL; DR

Навіть у невеликих проектах незмінність може бути корисною, але не вважайте, що вона існує для вас.

Значно, набагато довше відповідь

ПРИМІТКА. Для цілей цієї відповіді я використовую слово "дисципліна", щоб означати самозречення для певної користі.

Це за формою схоже на інше запитання: "Чи слід використовувати Typescript? Чому в JavaScript так важливі типи?". На це є і аналогічна відповідь. Розглянемо наступний сценарій:

Ви є єдиним автором та підтримувачем кодової бази JavaScript / CSS / HTML, що містить близько 5000 рядків. Ваш напівтехнічний начальник щось читає про Typescript-як-новий-гарячий і пропонує, що ми можемо захотіти перейти до нього, але залишає рішення вам. Отже, ти читаєш про це, граєш з ним тощо.

Тож тепер у вас є вибір зробити, чи переходите ви до Typescript?

Typescript має деякі переконливі переваги: ​​інтелігенція, швидке виявлення помилок, вказівка ​​ваших API заздалегідь, простота виправлення речей, коли рефакторинг порушує їх, менша кількість тестів. Typescript також має певні витрати: певні дуже природні та правильні ідіоми JavaScript можуть бути складними для моделювання в його не особливо потужній системі типів, анотації збільшують LoC, час та зусилля на перезапис існуючої бази коду, додатковий крок у розробці конвеєра тощо. Більш принципово, він вирізає підмножину можливих правильних програм JavaScript в обмін на обіцянку, що ваш код, швидше за все, буде правильним. Це довільно обмежує. У цьому і полягає вся суть: ви нав'язуєте деяку дисципліну, яка вас обмежує (сподіваємось, від себе стріляти в ногу).

Повернімось до питання, перефразованого в контексті вищевказаного абзацу: чи варто цього ?

В описуваному сценарії я заперечую, що якщо ви дуже добре знайомі з невеликою середньою базою коду JS, вибір використання Typescript є більш естетичним, ніж практичним. І це добре , в естетиці нічого поганого немає , вони просто не обов'язково переконливі.

Сценарій B:

Ви змінюєте роботу та зараз працюєте програмістом у Foo Corp. Ви працюєте з командою з 10 осіб на 90000 LoC (і рахую) JavaScript / HTML / CSS кодову базу з досить складним конвеєром побудови за участю babel, webpack , набір поліфілів, реагують з різними плагінами, система управління державою, ~ 20 сторонніх бібліотек, ~ 10 внутрішніх бібліотек, плагіни редактора, як лінкер з правилами внутрішнього керівництва стилем, тощо.

Ще коли ви були хлопцем / дівчиною 5k LoC, це просто не мало значення. Навіть документація не була такою великою справою, навіть повернувшись до певної частини коду через 6 місяців, ви могли це зрозуміти досить легко. Але зараз дисципліна не просто приємна, а необхідна . Ця дисципліна може не включати Typescript, але , ймовірно, буде включати певну форму статичного аналізу, а також всі інші форми дисципліни кодування (документація, посібник зі стилів, сценарії побудови, регресійне тестування, CI). Дисципліна - це вже не розкіш , це необхідність .

Все це стосувалося і GOTOв 1978 році: ваша нігтьова маленька гра в блекджек в C могла використовувати GOTOлогіку спагетті і спагетті, і просто не так вже й складно було вибрати свій власний пригода через свій шлях, але як програми стали більшими і більш амбітного, добре, недисциплінованого використання GOTOне вдалося підтримати. І все це стосується незмінності сьогодні.

Як і статичні типи, якщо ви не працюєте на великій кодовій базі з командою інженерів, що її підтримують / розширюють, вибір використання незмінності є більш естетичним, ніж практичним: переваги все ще є, але можуть ще не перевершити витрат.

Але як і у всіх корисних дисциплінах, настає момент, коли це вже не є необов’язковим. Якщо я хочу підтримувати здорову вагу, то дисципліна, що стосується морозива, може бути необов’язковою. Але якщо я хочу бути спортсменом-конкурентом, мій вибір того, їсти морозиво чи ні, мій вибір цілей. Якщо ви хочете змінити світ за допомогою програмного забезпечення, незмінність може бути частиною того, що вам потрібно, щоб він не руйнувався під власною вагою.


1
+1 Мені це подобається. Набагато більше на тему Джаред. І все ж незмінність не врятує команду від власної недисциплінованості. 😉
Стівен де Салас

@StevendeSalas - це форма дисципліни. І як такий, я думаю, що це співвідноситься з (але не замінює) іншими формами програмної дисципліни. Це доповнює, а не замінює. Але, як я вже говорив у коментарі до вашої відповіді, я зовсім не здивований, що його підштовхує технічний гігант із набором інженерів, які все шліфують на одній величезній кодовій базі :) їм потрібна вся дисципліна, яку вони можуть отримати. Я здебільшого не мутую об’єкти, але також не використовую жодної форми примусового виконання, адже це просто я.
Джаред Сміт

0

Я створив рамкову агностічну відкриту коду (MIT) для змінного (або незмінного) стану, який може замінити всі ті незмінні сховища, як libs (redux, vuex і т. Д.).

Незмінні стани були для мене некрасивими, оскільки надто багато роботи було зроблено (багато дій для простих операцій читання / запису), код був менш читабельним, а продуктивність для великих наборів даних була неприйнятною (повний рендеринг компонента: /).

За допомогою глибокого стану-спостерігача я можу оновити лише один вузол з крапковими позначеннями та використовувати підстановку. Я також можу створити історію стану (скасувати / повторити / подорож у часі), зберігаючи лише ті конкретні значення, які були змінені {path:value}= менше використання пам'яті.

Завдяки спостережувачу глибокого стану я можу налагоджувати речі, і я контролюю поведінку компонентів, тому продуктивність може бути кардинально покращена. Код легше читається, а рефакторинг - це набагато простіше - просто шукайте та замінюйте рядки шляху (не потрібно змінювати код / ​​логіку).


-1

Я думаю, що основною причиною незмінних об'єктів є збереження стану об'єкта дійсним.

Припустимо, у нас є об'єкт під назвою arr. Цей об'єкт дійсний, коли всі елементи є однією буквою.

// this function will change the letter in all the array
function fillWithZ(arr) {
    for (var i = 0; i < arr.length; ++i) {
        if (i === 4) // rare condition
            return arr; // some error here

        arr[i] = "Z";
    }

    return arr;
}

console.log(fillWithZ(["A","A","A"])) // ok, valid state
console.log(fillWithZ(["A","A","A","A","A","A"])) // bad, invalid state

якщо arrстане непорушним об'єктом, тоді ми будемо впевнені, що arr завжди знаходиться у дійсному стані.


Я думаю, що arrfillWithZ
мутується

якщо ви використовуєте imtable.js, ви отримуватимете нову копію об'єкта щоразу, коли змінюватимете його. тож оригінальний об’єкт залишається недоторканим
бедорлан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.