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


206

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

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

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

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



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

Я предоставил Redux точку зрения @bozzmob.
Прости

1
Может быть полезно узнать об неизменности в целом как о концепции функциональной парадигмы, а не пытаться думать, что JS имеет к этому какое-то отношение. Реакт написан любителями функционального программирования. Вы должны знать то, что они знают, чтобы понять их.
Герман

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

Ответы:


196

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

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

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

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

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

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

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

Все обновления возвращают новые значения, но внутренние структуры используются совместно, чтобы значительно сократить использование памяти (и перебор 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();

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


1
@naomik спасибо за отзыв! Мое намерение состояло в том, чтобы проиллюстрировать концепцию и явно показать, что Объекты не мутируют и не обязательно показывать, как реализовать это полностью. Тем не менее, мой пример может быть немного запутанным, я обновлю его немного.
Даниллуз

2
@bozzmob, пожалуйста! Нет, это не правильно, вам нужно самим установить неизменность в редукторе. Это означает, что вы можете следовать стратегиям, показанным в видео, или использовать библиотеку, например, immutablejs. Вы можете найти больше информации здесь и здесь .
Даниллуз

1
@naomik ES6 constне об неизменности. Об этом Матиас Биненс написал отличную статью в блоге .
Леа Розема

1
@terabaud спасибо, что поделились ссылкой. Я согласен, что это важное различие. ^ _ ^
Спасибо,

4
Пожалуйста, объясните это: «Мутация скрывает изменения, которые создают (неожиданные) побочные эффекты, которые могут вызвать неприятные ошибки. Когда вы применяете неизменность, вы можете сохранять архитектуру своего приложения и ментальную модель простыми, что упрощает рассуждение о вашем приложении». Потому что это совсем не так в контексте JavaScript.
Павле Лекич

143

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

TL / DR: неизменность - это скорее модная тенденция, чем необходимость в JavaScript. Если вы используете React, он обеспечивает удобный обход некоторых запутанных вариантов дизайна в управлении состоянием. Тем не менее, в большинстве других ситуаций он не принесет достаточной пользы по сравнению со сложностью, которую он представляет, и служит скорее для подбора резюме, чем для удовлетворения фактической потребности клиента.

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

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

Ну, я рад, что ты спросил!

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

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

Как ни печально, мода правит миром JavaScript. Теперь Абрамова провозглашают полубогом, и все мы, простые смертные, вынуждены подчиняться Дао Неизменности ... имеет ли это смысл или нет.

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

Ничего!

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

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

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

ДА! .. Конечно, это так!

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

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

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

Или в качестве альтернативы ...

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

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

Мода приходит и уходит, приятель. Есть много способов снять кожу с кошки.

Я сожалею о том, что вам приходится мириться с путаницей постоянно меняющегося набора парадигм программирования. Но привет, добро пожаловать в клуб!

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

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

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

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

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

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

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

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

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

Это довольно запутанное утверждение просто означает, что с React ваше состояние приложения станет более подверженным гонкам , но эта неизменность позволяет вам избавиться от этой боли. Зачем? Поскольку React является особенным ... он был спроектирован как высокооптимизированная библиотека рендеринга, в которой согласованное управление состоянием занимает второе место, и, таким образом, состояние компонента управляется через асинхронную цепочку событий (так называемое "одностороннее связывание данных"), которую вы не можете контролировать и полагаться на то, что вы помните, чтобы не изменять состояние напрямую ...

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

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

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

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

Знаешь. Реальность. Но эй, кого это волнует?

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

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

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

Потому что ... это особенность номер один, которую попросит менеджер проекта, верно?

6) Неизменное состояние имеет много интересных возможностей в сочетании с WebSockets

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

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

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


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

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

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

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


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


10
Привет Стивен, Да. У меня были все эти сомнения, когда я рассматривал immutable.js и redux. Но ваш ответ удивителен! Это добавляет большую ценность и спасибо за решение каждого вопроса, в котором у меня были сомнения. Теперь все намного яснее / лучше даже после нескольких месяцев работы с неизменными объектами.
bozzmob

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

6
Я сильно подозреваю, что взгляды на неизменяемость довольно четко соотносятся с размерами команды и кодовой базы, и я не думаю, что это совпадение, что основным сторонником является гигант из Кремниевой долины. При этом я с уважением не согласен: неизменность - это полезная дисциплина, а не использование goto - полезная дисциплина. Или юнит-тестирование. Или TDD. Или статический анализ типа. Это не значит, что вы делаете их постоянно, каждый раз (хотя некоторые делают). Я также сказал бы, что полезность ортогональна обману: в матрице полезного / излишнего и сексуального / скучного есть множество примеров каждого. "hyped" !== "bad"
Джаред Смит

4
Привет @ftor, хорошая точка зрения, слишком далеко в другом направлении. Однако, поскольку существует такое изобилие статей и аргументов «про неизменяемость в javascript», я чувствовал, что мне нужно сбалансировать вещи. Таким образом, у новичков есть противоположная точка зрения, чтобы помочь им принять решение в любом случае.
Стивен де Салас

4
Информативный и блестяще озаглавленный. Пока я не нашел этот ответ, я думал, что я единственный, кто придерживается подобного мнения. Я осознаю ценность неизменности, но меня беспокоит то, что это стало такой догмой, подавляющей все другие техники (например, в ущерб двустороннему связыванию, которое безумно полезно для форматирования ввода, как реализовано, например, в KnockoutJS).
Tyblitz

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 - если у вас есть 10 000 элементов в списке и добавлено еще 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 и неизменяемость являются мощными полезными парадигмами, позволяющими избежать повреждения данных и / или блокировок ресурсов в многопоточных средах, но не в JavaScript, потому что они однопоточные. Если я не пропускаю какой-то священный самородок мудрости, главный компромисс здесь заключается в том, готовы ли вы сделать свой код более сложным (и более медленным) в поиске, чтобы избежать условий гонки ... что является гораздо меньшей проблемой, чем большинство людей считать.
Стивен де Салас

37

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

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

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

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

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

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


1
Гениальный пример, который я говорю. Мое понимание неизменности теперь стало более ясным. Спасибо, Джаред. На самом деле, одна из реализаций - кнопка ОТМЕНА: D И вы сделали все довольно просто для меня.
bozzmob

3
То, что шаблон имеет смысл в git, не означает, что одно и то же имеет смысл везде. В git вы на самом деле заботитесь обо всей сохраненной истории и хотите иметь возможность объединять разные ветви. Во внешнем интерфейсе вы не заботитесь о большей части истории государства и вам не нужна вся эта сложность.
Ски

2
@ Ски это только сложно, потому что это не по умолчанию. Я обычно не использую mori или immutable.js в своих проектах: я всегда не решаюсь брать сторонние программы. Но если бы это было по умолчанию (а-ля 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 с символическими ссылками (на основе inode).


5

Еще одно преимущество неизменяемости в 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-й абзац имеет столько смысла. Спасибо вам за это. «Если вы хотите« отменить »какое-то состояние, вы можете создавать транзакции» !!
Бузмоб

Кстати, для ООП можно сделать сравнение с марксизмом. Помните Java? Черт, странные кусочки Java в JavaScript? Обман никогда не бывает хорошим, он вызывает радикализацию и поляризацию. Исторически ООП был намного более раскручен, чем реклама Redux в Facebook. Хотя они наверняка старались изо всех сил.
XIMO

4

Другой дубль ...

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

TL; DR

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

Гораздо дольше отвечу

ПРИМЕЧАНИЕ: для целей этого ответа я использую слово «дисциплина», чтобы означать самоотречение для некоторой выгоды.

По форме это похоже на другой вопрос: «Должен ли я использовать Typescript? Почему типы так важны в JavaScript?». У него тоже есть аналогичный ответ. Рассмотрим следующий сценарий:

Вы являетесь единственным автором и сопровождающим базы кода JavaScript / CSS / HTML, насчитывающей около 5000 строк. Ваш полу-технический начальник читает что-то о Typescript-as-new-hotness и предлагает, чтобы мы могли перейти к нему, но оставляем решение за вами. Итак, вы читаете об этом, играете с ним и т. Д.

Итак, теперь у вас есть выбор, вы переходите на Typescript?

Typescript имеет ряд неоспоримых преимуществ: интеллектуальный смысл, раннее обнаружение ошибок, предварительное указание API-интерфейсов, простота исправления ошибок при их рефакторинге, меньшее количество тестов. Typescript также имеет некоторые издержки: некоторые очень естественные и правильные идиомы JavaScript могут быть сложны для моделирования в его не особенно мощной системе типов, аннотации увеличивают LoC, время и усилия переписывают существующую кодовую базу, дополнительный шаг в конвейере сборки и т. Д. Более фундаментально, он выделяет подмножество возможных правильных программ на JavaScript в обмен на обещание, что ваш код, скорее всего, будет правильным. Это произвольно ограничительно. В этом весь смысл: вы навязываете некоторую дисциплину, которая ограничивает вас (надеюсь, от выстрела в ногу).

Вернемся к вопросу, перефразированному в контексте вышеприведенного абзаца: стоит ли это того ?

В описанном сценарии я бы сказал, что если вы хорошо знакомы с JS-базой кодов JS среднего размера, то выбор Typescript будет более эстетичным, чем практическим. И это нормально , в эстетике нет ничего плохого , просто они не обязательно убедительны.

Сценарий Б:

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

Когда вы были 5k LoC парень / девушка, это не имело большого значения. Даже документация не была , что большое дело, даже возвращаясь к определенной части кода через 6 месяцев вы могли понять его достаточно легко. Но теперь дисциплина не только хороша, но и необходима . Эта дисциплина может не включать Typescript, но , вероятно, будет включать некоторую форму статического анализа, а также все другие формы дисциплины кодирования (документация, руководство по стилю, сценарии сборки, регрессионное тестирование, CI). Дисциплина уже не роскошь , а необходимость .

GOTOКо всему этому относились в 1978 году: ваша изящная маленькая игра в блэкджек на C могла использовать GOTOлогику s и спагетти, и было не так уж и сложно выбрать приключение на своем пути, но по мере того, как программы становились больше и более амбициозное, ну, недисциплинированное использование GOTOне может быть устойчивым. И все это относится к неизменности сегодня.

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

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


1
+1 мне нравится Гораздо больше по делу, Джаред. И все же неизменность не спасет команду от недостатка дисциплины. Ste
Стивен де Салас

@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 всегда находится в допустимом состоянии.


Я думаю, что arrмутирует каждый раз, когда вы звонитеfillWithZ
Родриго-Сильвейра

если вы используете immutable.js, вы будете получать новую копию объекта каждый раз, когда вы меняете его.
Таким
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.