I. Что такое неизменность
II.Почему неизменность
III.Как реализовать неизменяемость
Я. Что такое неизменность
Неизменяемость означает, что однажды созданные данные не могут быть изменены.
ex: const age= 25; age = age + 1 // error
II. Почему неизменность
• Предсказуемость
JavaScript очень гибкий, это означает, что любой объект можно на лету преобразовать во что-то другое. В одной строке это может быть объект, представляющий собаку, а в следующей строке собака может быть мутирована в курицу. Такая ситуация усиливает опасения по поводу формы состояния и предсказуемости кода.
let number = 5; number = “Linda”; // js is a loosely typed language.
• Производительность
Несмотря на то, что добавление значений к неизменяемому объекту означает, что необходимо создать новый экземпляр, где необходимо скопировать существующие значения и добавить новые значения к новому объекту, что требует затрат памяти, неизменяемые объекты могут использовать структурное совместное использование для уменьшения памяти накладные расходы.
Чтобы узнать больше о структурном разделении: https://hackernoon.com/how-immutable-data-structures-e-g-immutable-js-are-optimized-using-structural-sharing-e4424a866d56
• Отслеживание мутаций
Неизменяемость позволяет оптимизировать приложение, используя равенство ссылок и значений. Это позволяет легко увидеть, изменилось ли что-нибудь. Например, изменение состояния компонента реакции. Вы можете использовать shouldComponentUpdate для проверки идентичности состояния путем сравнения объектов состояния и предотвращения ненужного рендеринга. Это также используется для выполнения отладки путешествия во времени в redux.
III. Как реализовать неизменяемость
1. Использование правильных конструкций es6 / языка
const, оператор распространения, присвоение, замораживание, фрагмент, concat, JSON.stringify () / parse (), карта, фильтр, сокращение
sort, reverse, push и pop изменяют исходный массив.
2. Функциональное программирование
3. Использование неизменяемой библиотеки
• immutable.js - реализация Facebook с JS-подобным API.
• Mori - постоянные структуры данных ClojureScript, экспортируемые в JS.
1. Использование правильных конструкций es6 / языка
Оператор распространения (…): использование оператора распространения для массивов и объектов.
const business_days = [‘Mon’, ‘Tue’, ‘Wed’, ‘Thu’, ‘Fri’]; const week_ends = [‘Sat’, ‘Sun’]; const days = […business_days, …week_ends]; console.log(“days:”, days); //[“Mon”, ”Tue”, ”Wed”, ”Thu”, ”Fri”, ”Sat”, ”Sun”];
- Используйте оператор распространения в качестве альтернативы push (), который изменяет исходный массив.
Object.freeze (): этот метод замораживает объект. Замороженный объект больше нельзя изменить. Это предотвращает добавление к нему новых свойств и удаление существующих свойств. Это также предотвращает изменение его прототипа.
const person = { name: ‘John’ }; person.name = ‘Linda’ person.gender = ‘female’; console.log (person); Object.freeze(person); person.name = ‘Smith’; person.gender = ‘Male’; console.log (person); // changes made after freeze will not be applied as the object is frozen.
Минусы: неудобно, так как замороженный объект выглядит как обычный объект.
Object.assign (): копирует значения из одного или нескольких исходных объектов в целевой объект. Он имеет подпись Object.assign (цель,… источники).
• Объединить объект
let first = {name: ‘Linda’}; let last = {lastName: ‘Smith’}; let fullName = Object.assign(first, last); console.log(fullName);
• Клонировать объект
let obj = {person: ‘Thor Odinson’}; let clone = Object.assign({}, obj); console.log(clone);
slice (): метод slice () возвращает выбранные элементы в массиве как новый объект массива.
var fruits = [“Banana”, “Orange”, “Lemon”, “Apple”, “Mango”]; var citrus = fruits.slice(1, 3); console.log(fruits); // [“Banana”, “Orange”, “Lemon”, “Apple”, “Mango”] console.log(citrus); // [“Orange”, “Lemon”]
• Slice можно использовать вместе с другими функциями, чтобы избежать мутации.
console.log(fruits.slice().reverse()); //same is applicable to sort, push and pop
Concat (): метод concat () используется для объединения двух или более массивов. Этот метод не изменяет существующие массивы, но возвращает новый массив, содержащий значения объединенных массивов.
const array1 = [‘a’, ‘b’, ‘c’]; const array2 = [‘d’, ‘e’, ‘f’]; console.log(array1.concat(array2)); // [“a”, “b”, “c”, “d”, “e”, “f”] console.log(array1); // [“a”, “b”, “c”] console.log(array2); // [“d”, “e”, “f”]
JSON.parse () и JSON.stringify (): еще один способ скопировать объект - использовать JSON.stringify и выполнить синтаксический анализ без изменения исходного объекта.
var obj1 = { sandwich: ‘turkey’, soda: ‘Pepsi’, chips: ‘Cape Cod’ }; var obj2 = JSON.parse(JSON.stringify(obj1)); obj2.cookie = ‘chocolate chip’; //Add an item to object 2 console.log(obj1); //logs {sandwich: “turkey”, soda: “Pepsi”, chips: “Cape Cod”} console.log(obj2); // logs {sandwich: “turkey”, soda: “Pepsi”, chips: “Cape Cod”, cookie: “chocolate chip”}
Map (), filter (), every (), some () и reduce ():
const numbers = [1,2,3,4,5,6,7];
// Map () - отображает значения, применяя условие без изменения исходного массива
const doubleNumbers = numbers.map(x => x* 2);
// Фильтр () - фильтрует значения массива без изменения исходного массива
const evenNumbers = numbers.filter(x => x%2 === 0);
// Некоторые - возвращает истину, если хотя бы один элемент удовлетворяет заданному условию.
const result = employees.some(employee => employee.sal > 500,000)
// Уменьшить - принимает массив данных и сводит к одному значению
const numbers = [1,2,3,4,5]; const sum = numbers.reduce((acc, number) => { return acc+ number; }, 0) console.log(sum); //15
2. Функциональное программирование:
- Это процесс создания программного обеспечения путем составления чистых функций, избегая общего состояния, изменяемых данных и побочных эффектов.
Плюсы:
• Декларативная
• Сосредоточен на том, «что»
• Чистые функции без побочных эффектов
• Неизменяемость
• Легко тестируемый код
• Разделение функций и данных - функции полностью отличаются от данных, с которыми они работают. Данные должны быть отправлены в качестве аргумента
• Первоклассные функции
• Функции высшего порядка
Пользователи функционального программирования указывают на функции стрелок, чтобы код оставался ясным и кратким.
Традиционный подход:
function add(arg1, arg2) { return arg1 + arg2; } const addNumbers = function(arg1, arg2) { return arg1 + arg2; }
Функциональный подход:
myFunction = (arg1, arg2) => arg1 + arg2;
Первоклассные функции: функции, возвращающие другую функцию (создатели функций).
const double = x => x *2; const triple = x => x * 3; const quadruple = x=> x * 4;
Вышеупомянутые 3 функции могут быть переписаны с использованием первоклассных функций.
const createMultiplier = y => x => x * y const double = createMultiplier(2); const triple = createMultiplier(3); const quadruple = createMultiplier(4); console.log(double(3));
Функции высшего порядка. Функции высшего порядка принимают функцию в качестве аргумента и возвращают функции.
const divide = (x,y) => x / y; const secondArgumentIsntZero = func => (…args) => { if (args[1] === 0) { console.log(“Error: dividing by zero’); return null; } return func(…args); } const divideSafe = secondArgumentIsntZero(divide);// function is sent as an argument. console.log(divideSafe(7,0));
3. Использование библиотеки Immutability.js:
• Гарантированная неизменяемость. Данные, инкапсулированные в объекте Immutable.JS, никогда не изменяются. Всегда возвращается новая копия.
• Богатый API - Immutable.JS предоставляет богатый набор неизменяемых объектов для инкапсуляции ваших данных - Карты, Списки, Наборы, Записи
• Производительность - Immutable.JS выполняет много негласной работы, чтобы оптимизировать производительность, сводя к минимуму необходимость копирования данных.
• Минусы: вы больше не сможете ссылаться на свойства объекта с помощью стандартной точки или скобки JavaScript. Вместо этого вы должны ссылаться на них через Immutable.JS get ()
• Невозможно использовать операторы распространения.
Пример неизменяемой структуры данных
Ссылка: https://immutable-js.github.io/immutable-js/
Образец кода:
•Список:
const list = Immutable.List([‘Hello’]); const updatedList = list.push(‘World’); console.log(list.toJS()); console.log(updatedList.toJS());
•Записывать:
const Person = Immutable.Record({name: ‘John’, age: 42}); const bob = new Person({name: ‘bob’}); console.log(bob.toJS());
•Карта
const map = Immutable.Map(); const updatedMap = map.set(‘first’, ‘1’) .set(‘second’, ‘2’); const mergedMap = updatedMap.merge({first: ‘11’, third: ‘3’}); console.log(“original map:”, map.toJS()); console.log(“updated map:”,updatedMap.toJS()); console.log(“merged map:”,mergedMap.toJS()); mergedMap.forEach((value) => { console.log(value); });
Другие способы повышения неизменяемости:
Использование плагинов esLint для реагирования приложений во избежание случайной мутации: плагины esLint помогают напоминать о случайных мутациях. Установите eslint-plugin-immutable как зависимость разработчика для отслеживания мутаций
›Npm install - save-dev eslint-plugin-immutable