简体   繁体   English

当语言没有提供任何明显的实现不变性的方法时,人们如何在 JavaScript 中实现不可变数据结构?

[英]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:我决定尝试使用函数式编程范例,因为我从几个来源听到并阅读到函数式编程创建的代码:

  • I have more control over.我有更多的控制权。
  • Is easily tested.很容易测试。
  • Easy for other people to read & follow.易于其他人阅读和关注。
  • Easier for me to read.我更容易阅读。
  • and can make certain applications more robust并且可以使某些应用程序更加健壮

...who wouldn't want that? ……谁不想呢? Of course I gave it a shot.我当然试了一下。


My First Attempt at Functional Programming in JavaScript我第一次尝试在 JavaScript 中进行函数式编程

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阻止您重新分配变量,但是不是从变异它等)。


So, how can you do Immutable JS?那么,你怎么能做不可变的 JS 呢?

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”是因为您可以(在某种程度上)通过第三方库弥补一流语言结构的不足(并且有一些非常令人印象深刻的成功案例)。

But how Immutability works?但是不变性是如何工作的呢?

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. ...嗯,答案很简单,接受函数式编程的一些基本原则,让第三方库填补这些语言空白。

  1. Separate the 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 });
  1. Avoid imperative approaches and leverage 3rd party dedicated libraries避免命令式方法并利用第三方专用库
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

Perhaps a note on some of the libs I love也许是关于我喜欢的一些库的注释

  1. 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-jsfp-ts也是非常成功的故事)
  2. RxJS enables immutable and side-effects free programming with sequences while also providing lazy evaluation mechanisms, etc. RxJS支持使用序列进行不可变和无副作用的编程,同时还提供惰性求值机制等。
  3. Redux and XState provide a solution for immutable state management. ReduxXState为不可变的 state 管理提供了解决方案。

And a final example最后一个例子

 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>

To finally reiterate on your own example最后重申你自己的例子

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

Although JavaScript lacks immutable built-in data structures, immutable state is still possible.尽管 JavaScript 缺少不可变的内置数据结构,但不可变的 state 仍然是可能的。

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 中以不可变样式进行编程时,您可能会遇到一些缺点:

  • JavaScript is optimized for mutation, not immutability. JavaScript 针对突变而不是不变性进行了优化。 So there may be a memory/performance penalty.所以可能会有内存/性能损失。 The immutable paradigm prefers generating new values over mutating existing values.不可变范式更喜欢生成新值而不是改变现有值。 (On the other hand Lisp-like languages are optimized for a bias towards immutability over mutation.) (另一方面,类似 Lisp 的语言针对突变的不变性进行了优化。)
  • Mutation may "leak" into your program if you use any of the many JavaScript primitives that mutate data (like Array.sort() ).如果您使用许多对数据进行变异的 JavaScript 原语中的任何一个(如Array.sort() ),变异可能会“泄漏”到您的程序中。

Libraries that assist in using the immutable paradigm in JS有助于在 JS 中使用不可变范式的库

Immer沉浸式

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不可变的.js

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

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。)

List of JavaScript immutable libraries JavaScript 不可变库列表

This list of immutable libraries is divided into two main categories:这个不可变库列表分为两大类:

  • Persistent Data Structures w/structural sharing具有结构共享的持久数据结构
  • Immutable helpers (simply shallow copy JS objects)不可变助手(简单的浅拷贝 JS 对象)

An even longer list of resources for functional programming in JavaScript JavaScript 中函数式编程的资源列表更长

https://project-awesome.org/stoeffel/awesome-fp-js https://project-awesome.org/stoeffel/awesome-fp-js

Finally, ProseMirror is good real-world example of immutable state in JavaScript:最后,ProseMirror 是 JavaScript 中不可变 state 的真实示例:

ProseMirror is an editor written in JavaScript that uses a persistent data structure to store the document data . ProseMirror 是用 JavaScript 编写的编辑器,它使用持久数据结构来存储文档数据

  • Note this data structure is immutable by convention.请注意,按照惯例,此数据结构是不可变的。 The developer must ensure the data structure is not mutated.开发人员必须确保数据结构不会发生变异。 It is possible to mutate the data structure, but the results are undefined and most likely undesired.可以改变数据结构,但结果是未定义的,很可能是不希望的。 So ProseMirror provides documentation and functions to support immutability.所以 ProseMirror 提供了文档和函数来支持不变性。
  • It would be possible to enforce immutability of this data structure using Object.freeze() , but this is generally avoided because it incurs a huge performance penalty.可以使用Object.freeze()强制此数据结构的不变性,但通常会避免这样做,因为它会导致巨大的性能损失。
  • Also note immutability is not "all or nothing."另请注意,不变性不是“全有或全无”。 The main data structure is immutable, but mutable variables are used where they make more sense (like loop variables.)主要数据结构是不可变的,但可变变量用于更有意义的地方(如循环变量。)

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:类似于片段的东西:

See also...也可以看看...

 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 中的属性是不可变的。

Learn More学到更多

Private class fields in MDN, but it's on Stage 3. MDN 中的私有 class 字段,但它在第 3 阶段。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 实施javascript脚本时,选择和取消选择所有复选框不起作用 - select and unselect all checkboxes doesn't work when implementing javascript script Immutable.js:导出到数组时如何保持不变性? - Immutable.js: How to maintain immutability when exporting to array? 将 JavaScript 代码实现为 ReactJs 逻辑不起作用 - Implementing JavaScript code to ReactJs logic doesn't work 更新不可变对象而不破坏 javascript 中的不变性 - Update immutable object without breaking immutability in javascript 使用不可变数据结构时,Jest模拟函数参数不匹配 - Jest mock function arguments don't match when using immutable data structures 实现语言切换器 - Implementing Language switcher 在任何网络浏览器上通过点击图片来实现MIDI控制(Chrome无法正常运行) - Implementing MIDI control by clicking on image, on any web browser (chrome doesn't work) redux和不可变数据结构如何处理大型数据集? - How does redux and immutable data structures handle large data sets? 不可变 JS - 转换深度嵌套的 JS object 时如何保留类型并获得不变性? - Immutable JS - how to preserve Type and get immutability when converting a deeply nested JS object? 有什么方法可以只在 javascript 中的 beforeunload 事件上提供提示吗? - Is there any way to only sometimes offer a prompt on the beforeunload event in javascript?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM