Почему неизменность так важна (или необходима) в JavaScript?

86

В настоящее время я работаю над React JS и React Native. На полуочередной дороге я столкнулся с неизменностью или библиотекой Immutable-JS, когда я читал о реализации Facebook Flux и Redux.

Вопрос: почему важна неизменность? Что не так в мутирующих объектах? Разве это не делает вещи простыми?

Приведем пример, рассмотрим простое приложение News reader, при этом начальный экран представляет собой просмотр списка заголовков новостей.

Если я задал массив объектов со значением изначально, я не могу манипулировать им. Что говорит принцип непреложности? (Исправьте меня, если я ошибаюсь.) Но что, если у меня есть новый объект новостей, который нужно обновить? В обычном случае я мог бы просто добавить объект в массив. Как мне добиться в этом случае? Удалить хранилище и воссоздать его? Не добавляет ли объект в массив более дешевую операцию?

PS: Если этот пример не является правильным способом объяснить неизменность, пожалуйста, дайте мне знать, какой правильный практический пример.

  • 2
    Просто да. Просто сломать. Неизменность в основном означает, что вы можете верить, что она не изменится В зависимости от обстоятельств, это не только важно, но и важно.
  • 6
    Соответствующий: programmers.stackexchange.com/questions/151733/…
Показать ещё 2 комментария
Теги:
functional-programming
immutability
immutable.js

7 ответов

77
Лучший ответ

Недавно я изучал ту же тему. Я сделаю все возможное, чтобы ответить на ваши вопросы и попытаться поделиться тем, что я узнал до сих пор.

Вопрос: почему важна неизменность? Что не так в мутирующие объекты? Разве это не делает вещи простыми?

В основном это связано с тем, что неизменность повышает прозрачность, производительность (косвенно) и позволяет отслеживать мутации.

Предсказуемость

Mutation скрывает изменения, которые создают (неожиданные) побочные эффекты, которые могут вызвать неприятные ошибки. Когда вы обеспечиваете неизменность, вы можете сохранить свою архитектуру приложения и ментальную модель простой, что упростит ваше обоснование вашего приложения.

Производительность

Даже если добавление значений к неизменяемому объекту означает, что нужно создать новый экземпляр, где необходимо скопировать существующие значения и добавить новые значения в новый объект, стоимость которого стоит. Неизменяемые объекты могут использовать структурный обмен, чтобы уменьшить издержки памяти.

Все обновления возвращают новые значения, но внутренние структуры разделяются резко сокращают использование памяти (и перехват GC). Это означает, что если вы добавляете к вектору с 1000 элементами, он фактически не создает новый вектор 1001-элементов длинный. Скорее всего, внутри всего лишь несколько выделяются небольшие объекты.

Подробнее об этом можно прочитать здесь.

Отслеживание мутаций

Помимо сокращения использования памяти, неизменность позволяет оптимизировать ваше приложение, используя ссылочное и стоимостное равенство. Это позволяет легко увидеть, изменилось ли что-либо. Например, изменение состояния в реагирующем компоненте. Вы можете использовать shouldComponentUpdate для проверки идентичности состояния путем сравнения состояний объектов и предотвращения ненужного рендеринга. Вы можете прочитать об этом здесь.

Дополнительные ресурсы:

Если я задал сначала массив объектов со значением. Я не могу манипулировать им. Что говорит принцип неизменности, верно? (Правильно если я ошибаюсь). Но что, если у меня есть новый объект новостей, который должен обновляться? В обычном случае я мог бы просто добавить объект к массив. Как мне добиться в этом случае? Удалить хранилище и воссоздать его? Не добавляет ли объект в массив более дешевую операцию?

Да, это правильно. Если вы запутались в том, как реализовать это в своем приложении, я бы рекомендовал вам посмотреть, как redux делает это, чтобы ознакомиться с основными концепциями, это помогло меня много.

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

Существует отличный курс обучения на egghead.io, где Дэн Абрамов, автор редукса, объясняет эти принципы следующим образом (я немного изменил код, чтобы лучше соответствовать сценарию):

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();

Кроме того, эти видеоролики демонстрируют более подробную информацию о том, как достичь неизменности для:

  • 0
    Проголосуй за общий хороший ответ. Тем не менее, доведите ваш пример кода до конца. Таким образом, addNews возвращает новый массив новостей, и оригинальные news не затрагиваются, но что тогда? Вы собираетесь назначить это ... news2 ? И тогда следующий звонок - это news3 ? Кажется, есть ошибка в подходе для этого конкретного фрагмента кода. (Подсказка: имеет ли смысл использовать const для переменной состояния ? Состояние специально предназначено для изменения со временем; я бы даже сказал, неявно гарантировано)
  • 1
    @naomik спасибо за отзыв! Мое намерение состояло в том, чтобы проиллюстрировать концепцию и явным образом показать, что Объекты не мутируют, и не обязательно показывать, как реализовать их полностью. Тем не менее, мой пример может быть немного запутанным, я обновлю его немного.
Показать ещё 6 комментариев
36
Вопрос: почему важна неизменность? Что не так в мутирующих объектах? Разве это не делает вещи простыми?

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

Когда вы делаете все неизменным, это означает, что данные больше не могут быть изменены неожиданностью. Вы точно знаете, что если вы передадите значение в функцию, оно не может быть изменено в этой функции.

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

Примечание 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
    Я не согласен с этим ответом, но он не касается его части вопроса «Я хотел бы извлечь уроки из практического примера». Можно утверждать, что хорошая ссылка на список заголовков новостей, используемых в разных областях. «Мне нужно только один раз обновить список, и все, что ссылается на список новостей, будут обновлены бесплатно», - я думаю, что лучший ответ мог бы принять общую проблему, как он представил, и показать ценную альтернативу, которая использует неизменность.
  • 0
    ХОРОШО; Я постараюсь редактировать.
Показать ещё 5 комментариев
26

Несмотря на то, что другие ответы в порядке, чтобы ответить на ваш вопрос о практическом использовании (из комментариев по другим ответам), сделайте шаг за пределами своего текущего кода в течение минуты и посмотрите на вездесущий ответ прямо под носом: git. Что произойдет, если каждый раз, когда вы нажимаете фиксацию, вы перезаписываете данные в репозитории?

Теперь мы находимся в одной из проблем, с которыми сталкиваются неизменные коллекции: раздувание памяти. Git достаточно умен, чтобы не просто создавать новые копии файлов каждый раз при внесении изменений, он просто отслеживает различия.

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

Эта стратегия также достаточно эффективна для структур данных в памяти, поскольку существуют известные алгоритмы дерева-работы, которые работают в логарифмическом времени.

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

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

  • 0
    Гениальный пример, который я говорю. Мое понимание неизменности теперь стало более ясным. Спасибо, Джаред. На самом деле, одна из реализаций - кнопка ОТМЕНА: D И вы сделали все довольно просто для меня.
  • 3
    То, что шаблон имеет смысл в git, не означает, что одно и то же имеет смысл везде. В git вы на самом деле заботитесь обо всей сохраненной истории и хотите объединить разные ветви. Во внешнем интерфейсе вы не заботитесь о большей части истории государства и вам не нужна вся эта сложность.
Показать ещё 1 комментарий
23

Контральный взгляд на неизменность

Короткий ответ: неизменность - скорее модная тенденция, чем необходимость в JavaScript. Есть несколько узких случаев, когда это становится полезным (React/Redux в основном), хотя обычно по неправильным причинам.

Длинный ответ: читайте ниже.

Почему неизменность настолько важна (или нужна) в javascript?

Хорошо, я рад, что ты спросил!

Некоторое время назад очень талантливый парень, получивший название Дэн Абрамов написал javascript-систему управления состоянием, называемую Redux, который использует чистые функции и неизменность. Он также сделал несколько действительно классных видеороликов, которые сделали эту идею очень легким для понимания (и продажи).

Время было прекрасным. Новизна Angular затухала, и мир JavaScript был готов зафиксировать последнее, что имело правильную степень прохлады, и это библиотека была не только новаторской, но и отлично отлажена React, которая рекламировалась другим Силовая мощность в Силиконовой долине.

Как бы это ни было, правила моды в мире JavaScript. Теперь Абрамова приветствуют как полубога, и все мы, простые смертные, должны подчиняться Dao of Immutability... Уэтер имеет смысл или не.

Что не так в мутирующих объектах?

Ничего!

На самом деле программисты мутировали объекты для er... до тех пор, пока были объекты для мутаций. 50+ лет разработки приложений, другими словами.

И зачем усложнять вещи? Когда у вас есть объект cat, и он умирает, вам действительно нужен второй cat для отслеживания изменения? Большинство людей просто скажут cat.isDead = true и сделают это.

Нет (мутирующие объекты) сделать вещи простыми?

ДА!.. Конечно!

Специально в JavaScript, который на практике наиболее полезен, используется для рендеринга представления некоторого состояния, которое поддерживается в другом месте (например, в базе данных).

Что делать, если у меня есть новый объект новостей, который необходимо обновить?... Как мне добиться в этом случае? Удалить хранилище и воссоздать его? Не добавляет ли объект в массив более дешевую операцию?

Хорошо, вы можете перейти к традиционному подходу и обновить объект News, чтобы ваше представление в памяти этого объекта изменилось (и представление, отображаемое пользователю, или так можно было бы надеяться)...

Или альтернативно...

Вы можете попробовать сексуальный подход FP/Immutability и добавить свои изменения в объект News к массиву, отслеживающему каждое историческое изменение, чтобы затем вы могли перебирать массив и выяснять, каково должно быть правильное представление состояния (phew!).

Я пытаюсь узнать, что здесь. Пожалуйста, просветите меня:)

Моды приходят и уходят. Есть 10 способов скинуть кошку.

Мне жаль, что вы должны нести путаницу постоянно меняющегося набора парадигм программирования. Но привет, ДОБРО ПОЖАЛОВАТЬ В КЛУБ!!

Теперь несколько важных моментов, которые следует помнить в отношении неизменности, и вы получите эти брошенные на вас лихорадочные силы, которые могут получить только наивность.

1) Неизбежность является удивительной для избежания условий гонки в многопоточных средах.

Многопоточные среды (например, С++, Java и С#) виновны в практике блокировки объектов, когда несколько потоков хотят их изменить. Это плохо для производительности, но лучше, чем альтернатива повреждению данных. И все же не так хорошо, как сделать все неизменным (Господь хвалят Хаскелла!).

НО АЛАС! В JavaScript вы всегда работают в одном потоке. Даже веб-работники (каждый из них работает внутри отдельный контекст). Поэтому, поскольку в контексте выполнения (все эти прекрасные глобальные переменные и замыкания) вы не можете иметь состояние гонки, связанное с потоком, основной момент в пользу неизменности выходит из окна.

(Сказав это, есть преимущество в использовании чистых функций в веб-работниках, а это значит, что у вас не будет никаких ожиданий о том, как возиться с объектами в основном потоке.)

2) Невосприимчивость может (каким-то образом) избежать условий гонки в состоянии вашего приложения.

И вот в чем суть дела, большинство разработчиков (React) расскажут вам, что Immutability и FP могут каким-то образом справиться с этой магией, которая позволяет состоянию вашего приложения стать предсказуемым.

Конечно, это не означает, что вы можете избежать условий гонки в базе данных, чтобы вытащить тот, который вам нужен, чтобы координировать всех пользователей во всех браузерах, и для этого вам нужна технология push-end push, например WebSockets (подробнее об этом ниже), которая будет транслировать изменения для всех пользователей приложения.

Это довольно запутанное утверждение просто означает, что последние значения, назначенные объекту состояния (определенные одним пользователем, работающим в браузере), становятся предсказуемыми. На самом деле никакого прогресса нет. Поскольку вы могли бы использовать простую устаревшую переменную, чтобы отслеживать ваше состояние, и вы знаете, что вы имеете дело с последней информацией всякий раз, когда вы обращаетесь к ней, и это будет работать с чем угодно, кроме React/Redux.

Почему? Поскольку React является специальным.. и состояние компонента управляется через цепочку событий, которые вы не контролируете и полагаетесь на вас помня, что не нужно напрямую мутировать состояние. Это было изучено с точки зрения PR, так как реклама React превратила недостаток в сексуальную моду. Мода в сторону, я бы предпочел неизменность того, что она есть, т.е. Инструмент, чтобы подключить разрыв, когда ваш выбор фреймворка не обрабатывает состояние интуитивно.

3) Условия гонки категорически плохи.

Ну, они могут быть, если вы используете React. Но они встречаются редко, если вы выбираете другую структуру.

Кроме того, у вас обычно есть гораздо большие проблемы, чтобы справиться... Проблемы, такие как аддитивная ад. Как раздутая кодовая база. Как ваш CSS не загружается. Подобно медленному процессу сборки или привязанности к монолитному back-end, что делает итерацию практически невозможной. Как неопытные разработчики, не понимающие, что происходит и что-то путают.

Вы знаете. Реальность. Но эй, кому это нужно?

4) Невосприимчивость использует Справочные типы, чтобы снизить влияние производительности на отслеживание каждого изменения состояния.

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

5) Неизменность позволяет вам использовать материал UNDO.

Потому что er.. это функция номер один, которую попросит менеджер проекта, не так ли?

6) Неизменное состояние имеет много холодного потенциала в сочетании с WebSockets

Последнее, но не менее важное: накопление государственных дельт делает довольно убедительный случай в сочетании с WebSockets, что позволяет легко использовать состояние как поток непреложных событий...

Как только пенни падает на эту концепцию (состояние, являющееся потоком событий, а не грубый набор записей, представляющих последнее представление), неизменный мир становится волшебным местом для обитания. Земля event-sourced удивление и возможность, которая выходит за пределы самого времени. И когда все сделано правильно, это может сделать приложения в режиме реального времени более легкими для выполнения, вы просто передаете поток событий всем заинтересованным, чтобы они могли создавать собственное представление настоящего и записывать свои собственные изменения в общий поток.

Но в какой-то момент вы просыпаетесь и понимаете, что все это чудо и магия не приходят бесплатно, гораздо труднее написать неизменный код и намного легче сломать его, плюс нет смысла иметь непреложный интерфейс, если у вас нет поддержки для поддержки. И когда вы наконец убедите своих коллег заинтересованных сторон, что вы должны публиковать и потреблять события через push techology, например WebSockets, вы узнайте, что означает боль в масштабе производства.


Теперь для некоторых советов, если вы решите принять его.

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

Теперь, если вам посчастливилось иметь возможность делать выбор в вашей работе, попробуйте использовать свою мудрость (или нет) и сделайте то, что вы заплатите за человека. Вы можете основывать это на своем опыте, на вашей кишке или на том, что происходит вокруг вас (по общему признанию, если все используют React/Redux, тогда есть веский аргумент, что будет легче найти ресурс для продолжения вашей работы). Альтернативно, вы можете попробовать либо Resume Driven Development или Hype Driven Development подходов. Они могут быть больше вашего рода.

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


Я добавил это как статью в своем блоге = > Неизменность в JavaScript: противоположный вид. Не стесняйтесь отвечать там, если у вас есть сильные чувства, которые вы хотели бы получить от своей груди тоже;).

  • 7
    Привет Стивен, Да. У меня были все эти сомнения, когда я рассматривал immutable.js и redux. Но ваш ответ удивителен! Это добавляет большую ценность и спасибо за решение каждого вопроса, в котором у меня были сомнения. Теперь все намного яснее / лучше даже после нескольких месяцев работы с неизменными объектами.
  • 0
    Спасибо! На самом деле пришлось вывести это из моей системы. Я рад, что вышло конструктивно;)
Показать ещё 12 комментариев
4

Почему неизменность настолько важна (или необходима) в JavaScript?

Неизменяемость может отслеживаться в разных контекстах, но самое главное - отслеживать ее в зависимости от состояния приложения и от пользовательского интерфейса приложения.

Я рассмотрю шаблон JavaScript Redux как очень модный и современный подход и потому, что вы упомянули об этом.

Для пользовательского интерфейса нам нужно сделать предсказуемым. Это будет предсказуемо, если UI = f(application state).

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

Функция редуктора просто берет действие и старое состояние и возвращает новое состояние, сохраняя старое состояние неповрежденным.

new state  = r(current state, action)

Изображение 2709

Преимущество: вы перемещаете время по штатам, поскольку все объекты состояния сохраняются, и вы можете отображать приложение в любом состоянии, поскольку UI = f(state)

Таким образом, вы можете легко отменить/повторить.


Бывает создание всех этих состояний, которые по-прежнему могут быть эффективными с точки зрения памяти, аналогия с Git велика, и аналогичная аналогия аналогична ОС Linux с символическими ссылками (на основе inodes).

2

Еще одно преимущество неизменности в 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 является неизменным, то это не проблема.

-1

Я думаю, что главная причина pro неизменяемых объектов - сохранение состояния объекта.

Предположим, что у нас есть объект с именем 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 всегда находится в допустимом состоянии.

  • 0
    Я думаю, что arr мутирует каждый раз, когда вы вызываете fillWithZ
  • 0
    если вы используете immutable.js, вы будете получать новую копию объекта каждый раз, когда вы меняете его. поэтому оригинальный объект остается нетронутым

Ещё вопросы

Сообщество Overcoder
Наверх
Меню