不可变值(immutability)是一旦创建,就无法更改的值。
Primitives(原始值)
原始值是不可变的,对象不是。
不可变原语的最佳示例是string。 对字符串的任何修改都将产生一个新字符串。 考虑下面的例子:
"ABCD".substring(1,3) //"BC" "AB" + "CD" //"ABCD" "AB".concat("CD") //"ABCD"
在所有这些情况下,都会创建新字符串。
Reading code
看看下一个代码:
const arr = [1,2,3]; doSomething(arr); console.log(arr); //[1,2,3] ?
我们可以轻易地说log()会将[1,2,3]打印到控制台吗? 不是的。 该数组是JavaScript中的可变数据结构。 它可以在doSomething()函数中更改。 我们需要阅读并理解doSomething()函数,以便说出将在控制台中记录的内容。
以下是改变数组的doSomething()函数示例:
function doSomething(arr){ arr.push(4) }
如果数组是一个不可变的数据结构,我们可以说在没有读取doSomething()函数的情况下将在控制台中记录什么。
Const
const声明一个无法重新分配的变量。 只有当赋值是不可变的时,它才会变为常量。
简而言之,使用带有原始值的const定义一个常量。 将const与对象值一起使用不一定定义常量。
const book = ({ title : "JavaScript, The good parts", author : "Douglas Crockford" }); book.title = "Other title"; console.log(book);
Freezing objects
要使对象不可变,我们需要冻结它们。 使数组不可变需要更多的纪律。
Object.freeze()可用于冻结对象。 无法添加,删除或更改属性。 对象变得不可变。
const book = Object.freeze({ title : "How JavaScript Works", author : "Douglas Crockford" }); book.title = "Other title"; //Cannot assign to read only property 'title'
Object.freeze()执行浅冻结。 嵌套对象可以更改。 对于深度冻结,我们需要递归地冻结类型对象的每个属性。 以下是我们如何创建deepFreeze()实现。
function deepFreeze(object) { Object.keys(object).forEach(function freezeNestedObjects(name){ const value = object[name]; if(typeof value === "object") { deepFreeze(value); } }); return Object.freeze(object); }
deepFreeze()不会使数组成为不可变的。 它使普通对象不可变。
使用不可变对象
将book视为不可变对象。 任何更改都需要创建一个新对象
Edit property
const title = "JavaScript The good parts" const newBook = { ...book, title };
Add propery
const description = "Looking at JavaScript"; const newBook = { ...book, description };
Remove property
请参阅下文,如何使用解构语法创建没有title属性的新对象:
const { title, ...newBook } = book;
使用数组作为不可变的
数组数据结构在JavaScript中不是不可变的。 为了使用数组作为不可变数据结构,我们只需要使用纯数组方法和扩展运算符。 纯数组方法是在某些内容发生变化时创建新数组的方法。
- pop(),push()和splice()都不是纯粹的
- concat(),slice()是纯粹的
- sort()应该是纯粹的
Add
以下是向不可变数组添加新值的示例:
const books = [ { title : "book1" }, { title : "book2"}]; //add with spread const newBooks = [ ...books, newBook ]; //add with concat() const newBooks = books.concat([newBook]);
Remove
以下是从位置索引中删除值的示例:
const newBooks = [...books.slice(0, index), ...books.slice(index + 1)];
不可变的可用的库
Immutable.js (https://facebook.github.io/immutable-js/) 提供了不可变的数据结构,如List和Map。 这些数据结构经过高度优化。
让我们使用List数据结构。
Add
push(value)返回添加了新值的新List。
import { List, Map } from "immutable"; const aNewBook = { title: "book3" }; const books = List([{ title: "book1" }, { title: "book2" }]); const newBooks = books.push(aNewBook); newBooks.toArray();
Remove
remove(index)返回一个新的List,它排除索引位置的值。
const books = List([{ title: "book1" }, { title: "book2" }]); const remainingBooks = books.remove(0); remainingBooks.toArray();
接下来,让我们看一下Map:
const book = Map({ title: "JavaScript, The good parts", author: "Douglas Crockford" }); const title = "JavaScript The good parts" const newBook = book.set("title", title); console.log(newBook.toJS())