[英]How are people implementing immutable data structures in JavaScript when the language doesn't offer any obvious way of implementing immutability?
I decided to try using the functional programming paradigm because I have heard, and read, from several sources that functional programming creates code that:我决定尝试使用函数式编程范例,因为我从几个来源听到并阅读到函数式编程创建的代码:
...who wouldn't want that? ……谁不想呢? Of course I gave it a shot.我当然试了一下。
My first attempt at functional programming did not go well at all.我对函数式编程的第一次尝试完全没有 go 。 I thought of it in terms of state , and maintaining the applications state throughout, however, I quickly got stuck after writing no more than a few lines of code.我从state的角度来考虑它,并在整个过程中维护应用程序 state ,但是,我只写了几行代码就很快陷入困境。 I couldn't wrap my head around how to implement immutable data structures, that were truly immutable, or how to change a variable, within my data structure, when the variables, and their properties are all non-writable"_当变量及其属性都是不可写时,我无法理解如何实现真正不可变的不可变数据结构,或者如何在我的数据结构中更改变量“_
How do contemporary JavaScript developers implement immutable data structures for managing the state of their applications, when JavaScript doesn't offer any sort of explicit immutable data types, or support?当 JavaScript 不支持任何类型的显式不可变数据类型时,当代 JavaScript 开发人员如何实现不可变数据结构来管理其应用程序的 state?
An example of Any immutable data structure is what I am looking for;我正在寻找任何不可变数据结构的示例; as well as how to implement functions that allow me to make uses of the data structure, to manage the state of a JS Application.以及如何实现允许我使用数据结构的功能,以管理 JS 应用程序的 state。 If the answer involves the use 3rd party libraries, other languages, or any other tool that is perfectly fine by me.如果答案涉及使用 3rd 方库、其他语言或任何其他对我来说完全没问题的工具。 An example of actual code would be awesome, that way I have something to interpret and come to understand.一个实际代码的例子会很棒,这样我就有一些东西要解释和理解。
Bellow is my horrible attempt at creating an immutable data structure, that I could implement. Bellow 是我在创建一个我可以实现的不可变数据结构方面的可怕尝试。
Though its not good code, it demonstrates what I am trying to accomplish虽然它的代码不好,但它展示了我想要完成的事情
'use strict';
const obj = {};
Object.defineProperties(obj, {
prop_1: {
value: (str) => {this.prop_3 = str};
writable: false,
},
prop_2: {
value: () => this.prop_3;
writable: false,
},
prop_3: {
value: '',
writable: false,
},
});
obj.prop_1('apples & bananas');
console.log(obj.prop_3);
/*
TERMINAL OUTPUT:
Debugger attached.
Waiting for the debugger to disconnect...
file:///home/ajay/Project-Repos/j-commandz/sandbox.js:19
this.prop_3 = str;
^
TypeError: Cannot assign to read only property 'prop_3' of object '#<Object>'
at Object.set (file:///home/ajay/Project-Repos/j-commandz/sandbox.js:19:19)
at file:///home/ajay/Project-Repos/j-commandz/sandbox.js:37:5
*/
You are right, Javascript (unlike Haskell & co.) does not provide first-class support for Immutable Data Structures ( In Java you'd have the keyword final
).你是对的,Javascript(与Haskell & co.不同)不提供对不可变数据结构的一流支持(在 Java 中你会有关键字final
)。 This does not mean that you cannot write your code, or reason about your programs, in an Immutable fashion.这并不意味着您不能以不可变的方式编写代码或推理程序。
As others mentioned, you still have some native javascript APIs that help you with immutability(ish), but as you realized already, none of them really solve the problem ( Object.freeze
only works shallowly, const
prevents you from reassigning a variable, but not from mutating it, etc.).正如其他人提到的那样,您仍然有一些本机 javascript API 可以帮助您实现不变性(ish),但是正如您已经意识到的那样,它们都没有真正解决问题( Object.freeze
仅在浅层工作, const
阻止您重新分配变量,但是不是从变异它等)。
I'd like to apologise in advance, as this answer could result primarily opinion based and be inevitably flawed by my own experience and way of thinking.我想提前道歉,因为这个答案可能主要基于意见,并且由于我自己的经验和思维方式而不可避免地存在缺陷。 So, please, pick the following with a pinch of salt as it just is my two cents on this topic.所以,请选择以下几点,因为这只是我在这个话题上的两分钱。
I'd say that immutability is primarily a state of the mind, on top of which you can then build all the language APIs that support (or make easier to work with) it.我想说的是,不变性主要是思想的 state,然后您可以在其之上构建支持(或使其更易于使用)的所有语言 API。
The reason why I say that "it's primarily a state of the mind" is because you can (kind of) compensate the lack of first-class language constructs with third party libraries (and there are some very impressive success stories).我之所以说“它主要是头脑中的 state”是因为您可以(在某种程度上)通过第三方库弥补一流语言结构的不足(并且有一些非常令人印象深刻的成功案例)。
Well, the idea behind it is that any variable is treated as fixed and any mutation must resolve in a new instance, leaving the original input
untouched.好吧,它背后的想法是任何变量都被视为固定变量,并且任何突变都必须在新实例中解决,而原始input
保持不变。
The good news is that this is already the case for all the javascript primitives.好消息是所有 javascript 原语都已经是这种情况。
const input = 'Hello World';
const output = input.toUpperCase();
console.log(input === output); // false
So, the question is, how can we treat everything as it was a primitive ?所以,问题是,我们怎样才能把一切都当成原始的呢?
...well, the answer is quite easy, embrace some of the basic principles of functional programming and let third party libraries fill those language gaps. ...嗯,答案很简单,接受函数式编程的一些基本原则,让第三方库填补这些语言空白。
state
from their transition
logic:将state
与其transition
逻辑分开:class User {
name;
setName(value) { this.name = value }
}
to just只是
const user = { name: 'Giuseppe' };
const setUserName = (name, user) => ({ ...user, name });
import * as R from 'ramda';
const user = {
name: 'Giuseppe',
address: {
city: 'London',
}
};
const setUserCity = R.assocPath(['address', 'city']);
const output = setUserCity('Verbicaro', user);
console.log(user === output); // recursively false
Ramda
provides immutability as well as enriching the js api with all those declarative goodies that you'd normally find in any f language ( sanctuary-js and fp-ts are also great success stories) Ramda
提供了不变性并丰富了 js api 与您通常在任何f语言中找到的所有声明性好东西( sanctuary-js和fp-ts也是非常成功的故事)RxJS
enables immutable and side-effects free programming with sequences while also providing lazy evaluation mechanisms, etc. RxJS
支持使用序列进行不可变和无副作用的编程,同时还提供惰性求值机制等。Redux
and XState
provide a solution for immutable state management. Redux
和XState
为不可变的 state 管理提供了解决方案。 const reducer = (user, { type, payload }) => { switch(type) { case 'user/address/city | set': return R.assocPath(['address', 'city'], payload, user); default: return user; } } const initial = { name: 'Giuseppe', address: { city: 'Verbicaro', }, }; const store = Redux.createStore(reducer, initial); console.log('state', store.getState()); store.dispatch({ type: 'user/address/city | set', payload: 'London', }); console.log('state2', store.getState());
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.js" integrity="sha512-3sdB9mAxNh2MIo6YkY05uY1qjkywAlDfCf5u1cSotv6k9CZUSyHVf4BJSpTYgla+YHLaHG8LUpqV7MHctlYzlw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.1.0/redux.js" integrity="sha512-tqb5l5obiKEPVwTQ5J8QJ1qYaLt+uoXe1tbMwQWl6gFCTJ5OMgulwIb3l2Lu7uBqdlzRf5yBOAuLL4+GkqbPPw==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
const obj = { prop_1(value) { return {...this, prop_3: value } }, prop_2: () => this.prop_3, prop_3: '', } console.log(obj); const obj2 = obj.prop_1('Apple & Banana'); console.log(obj2);
As you know, variables store a program's state.如您所知,变量存储程序的 state。 Functional languages like Lisp usually change program state by taking the current state as input and returning the new updated state as output (which is used as input for another function; repeat). Functional languages like Lisp usually change program state by taking the current state as input and returning the new updated state as output (which is used as input for another function; repeat).
JavaScript programs usually change program state by mutating variables, but the method used by Lisp described above can also be used. JavaScript 程序通常通过变异变量来改变程序 state,但也可以使用上述 Lisp 使用的方法。 Instead of writing functions that mutate variables, simply write functions that input the current state and return a new output state without modifying any of the inputs.无需编写改变变量的函数,只需编写输入当前 state 并返回新 output state 的函数,而无需修改任何输入。
You may run into some drawbacks when programming in an immutable style in JavaScript:在 JavaScript 中以不可变样式进行编程时,您可能会遇到一些缺点:
Array.sort()
).如果您使用许多对数据进行变异的 JavaScript 原语中的任何一个(如Array.sort()
),变异可能会“泄漏”到您的程序中。Immer is a JS library that helps use immutable state in JS: Immer是一个 JS 库,有助于在 JS 中使用不可变的 state:
The basic idea is that you will apply all your changes to a temporary draftState, which is a proxy of the currentState.基本思想是将所有更改应用到临时草稿状态,它是 currentState 的代理。 Once all your mutations are completed, Immer will produce the nextState based on the mutations to the draft state.一旦你完成了所有的突变,Immer 将根据草案 state 的突变生成 nextState。 This means that you can interact with your data by simply modifying it while keeping all the benefits of immutable data.这意味着您可以通过简单地修改数据来与数据交互,同时保留不可变数据的所有好处。
Immutable.js is another JS library that helps implement immutable state in JS: Immutable.js是另一个帮助在 JS 中实现不可变 state 的 JS 库:
Immutable.js provides many Persistent Immutable data structures including: List, Stack, Map, OrderedMap, Set, OrderedSet and Record. Immutable.js 提供了许多 Persistent Immutable 数据结构,包括:List、Stack、Map、OrderedMap、Set、OrderedSet 和 Record。
These data structures are highly efficient on modern JavaScript VMs by using structural sharing via hash maps tries and vector tries as popularized by Clojure and Scala, minimizing the need to copy or cache data. These data structures are highly efficient on modern JavaScript VMs by using structural sharing via hash maps tries and vector tries as popularized by Clojure and Scala, minimizing the need to copy or cache data.
Mori extracts ClojureScript's optimized immutable data structures so you can use them in vanilla JS. Mori提取了 ClojureScript 优化的不可变数据结构,因此您可以在 vanilla JS 中使用它们。 (ClojureScript is a Lisp that compiles to JavaScript.) (ClojureScript 是一种 Lisp,可编译为 JavaScript。)
This list of immutable libraries is divided into two main categories:这个不可变库列表分为两大类:
https://project-awesome.org/stoeffel/awesome-fp-js https://project-awesome.org/stoeffel/awesome-fp-js
ProseMirror is an editor written in JavaScript that uses a persistent data structure to store the document data . ProseMirror 是用 JavaScript 编写的编辑器,它使用持久数据结构来存储文档数据。
Object.freeze()
, but this is generally avoided because it incurs a huge performance penalty.可以使用Object.freeze()
强制此数据结构的不变性,但通常会避免这样做,因为它会导致巨大的性能损失。JavaScript doesn't really have a way of implementing Immutable data, at least not a way that is obvious. JavaScript 并没有真正实现不可变数据的方法,至少没有明显的方法。
You might be new to JS, but the obvious way to make an object immutable is to freeze it:您可能是 JS 新手,但使 object 不可变的明显方法是冻结它:
const obj = Object.freeze({
prop_2() { return this.prop_3 }
prop_3: '',
});
obj.prop_3 = 'apples & bananas'; // throws as expected
console.log(obj.prop_3);
If objects are immutable, we need to create new ones with the new values that we want instead of assigning to their properties.如果对象是不可变的,我们需要使用我们想要的新值创建新对象,而不是分配给它们的属性。 Spread property syntax in object literals helps us to achieve this, as do helper methods: object 文字中的展开属性语法可以帮助我们实现这一点,辅助方法也是如此:
const base = {
withProp(newVal) {
return { withProp: this.withProp, prop: newVal };
},
prop: '';
};
const obj1 = base.withProp('apples & bananas');
console.log(obj1.prop);
const obj2 = {...base, prop: obj1.prop + ' & oranges'};
console.log(obj2.prop);
With enough self-restraint (or drill, or code reviews, or tooling like typecheckers and linters), this programming style of cloning objects becomes natural, and you won't make mistakes with accidental assignments any more.有了足够的自我约束(或钻孔、代码审查或类型检查器和 linter 之类的工具),这种克隆对象的编程风格就会变得自然,并且您不会再因意外分配而出错。
Of course, doing this with more complicated (nested) structures is cumbersome, so there exist quite some libraries that provide helper functions, and there are also implementations of more advanced purely functional data structures available that are more efficient than cloning your whole data every time.当然,用更复杂(嵌套)的结构来做这件事很麻烦,所以有相当多的库提供辅助函数,还有更高级的 纯函数数据结构的实现,它们比每次都克隆整个数据更有效.
One idea (of many) may be to wrap objects in a Proxy
.一个想法(许多)可能是将对象包装在Proxy
中。 Something like the snippet:类似于片段的东西:
function createObject(someObject) { const localScores = {value: {history: [], current: []}, writable: true}; const proxyHandler = { get: (target, prop) => { if (.(prop in target)) { console;log(`'${prop}' is a non existing property`); return null. } return target[prop];value, }: set, (obj, prop. value) => { if (obj[prop] && obj[prop].writable) { obj[prop];value = value; return value. } console,log(`'${prop}' is not writable; sorry`), }; }. return new Proxy( {..,someObject. ..:{ local, localScores } }; proxyHandler ): } const obj = createObject({ prop1: {value, 'some string 1': writable, false}: prop2: {value, '': writable, true}: prop3: {value, 42: writable, false}; }). obj;nothing. obj;prop1 = 'no dice'. obj;prop2 = 'apples & bananas.': obj.local = { history. [...obj,local.history. obj,local:current]. current. [...obj,local.current. .,,[1;2.3]] }: obj.local = { history. [...obj,local.history. obj,local:current]. current. [...obj,local.current; obj.prop3] }: obj.local = { history. [...obj,local.history. obj,local:current]. current. [...obj,local.current. .,;[123. 321]] }. console:log(`obj.prop1; ${obj.prop1}`). console:log(`obj.prop2 has a new value; ${obj.prop2}`). console:log(`obj.local. ${JSON;stringify(obj.local)}`);
Welcome to Functional Programming!欢迎来到函数式编程!
One solution is using ES6 class.一种解决方案是使用 ES6 class。 getter returns property's deep copy, setter throws error. getter 返回属性的深层副本,setter 抛出错误。
Example Code:示例代码:
class Person { _name = ""; constructor(name) { this._name = name; } get name() { return this.name; } set name(name) { throw new Error("Can't reassign a Person's name"); } } class ImmutableArray { _arr = []; constructor(arr) { this._arr = [...arr]; } get arr() { return [...this._arr]; } set arr(arr) { throw new Error("Can't reassign a ImmutableArray"); } } const aPerson = new Person("jiho"); aPerson.name = "W3Dojo"; // Error: Can't reassign a Person's name const aImmutableArray = new ImmutableArray([1, 2, 3]); aImmutableArray.arr = [2]; // Error: Can't reassign a ImmutableArray const arr = aImmutableArray.arr; arr[2] = 20; console.log(aImmutableArray.arr[2]); // 3
In this methodology, a property inside class is immutable.在此方法中,class 中的属性是不可变的。
Private class fields in MDN, but it's on Stage 3. MDN 中的私有 class 字段,但它在第 3 阶段。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.