带有MORI的不变数据和功能性JavaScript
钥匙要点
- > MORI利用Clojure的持久数据结构,为JavaScript开发人员不可变的数据选项提供了增强代码简单性和可靠性的不可能。 >
- 使用MORI通过强制性不变性来促进JavaScript中的功能编程范式,从而防止意外副作用并确保整个应用程序生命周期中的数据一致性。
库促进了处理数据的不同方法,其中函数分别在数据结构上运行,与JavaScript的典型对象方面的方法形成鲜明对比,从而允许使用更清洁,更可预测的代码。 - MORI的结构共享技术通过在可能的情况下重复现有数据结构来使数据操纵有效,从而可以改善应用程序的性能。
>通过诸如具有撤消功能的像素编辑器之类的示例,Mori展示了不变数据结构的实际应用,从而为开发人员提供了构建既复杂且稳健的功能的工具。- >。
- 本文由Craig Bilner和Adrian Sandu进行了同行评审。感谢SitePoint所有的同行评审员制作SitePoint内容的最佳状态!
>尽管JavaScript一直支持某些功能性编程技术,但它们在过去几年中才真正流行,传统上也没有对不变数据的本地支持。 JavaScript仍在学习很多方面,最好的想法来自已经尝试并测试了这些技术的语言。 在编程世界的另一个角落,Clojure是一种功能性编程语言,致力于真正的简单性,尤其是在数据结构的情况下。 Mori是一个库,允许我们直接从JavaScript中使用Clojure的持久数据结构。 >本文将探讨这些数据结构设计背后的基本原理,并检查一些使用它们来改善我们的应用程序的模式。我们也可以将其视为对使用Clojure或Clojurescript进行编程的JavaScript开发人员的第一个垫脚石。
>什么是持续数据?
>> clojure在无法更改的
持续的值之间进行区分,而
>可能有助于查看这种区别在理论编程语言中的外观。
<span>// transient list </span>a <span>= [1, 2, 3]; </span>b <span>= a.push(4); </span><span>// a = [1, 2, 3, 4] </span><span>// b = [1, 2, 3, 4] </span> <span>// persistent list </span>c <span>= #[1, 2, 3] </span>d <span>= c.push(4); </span><span>// c = #[1, 2, 3] </span><span>// d = #[1, 2, 3, 4] </span>
>我们可以看到,当我们将一个值推向其上时,瞬态列表被突变了。 A和B都指向相同的可变值。相比之下,在持久列表上呼叫推送返回了一个新值,我们可以看到c和d指向不同的离散列表。
>>这些持久的数据结构无法突变,这意味着一旦我们提到了一个值,我们也可以保证它永远不会被更改。这些保证通常可以帮助我们编写更安全,更简单的代码。例如,将持久数据结构作为参数的函数无法突变它们,因此,如果该函数想要传达有意义的更改,则必须来自返回值。这导致编写引用透明的纯函数,易于测试和优化。
更简单,不变的数据迫使我们编写更多功能代码。什么是mori?
> Mori使用clojurescript编译器来编译Clojure标准库中数据结构的实现,以汇编JavaScript。编译器发出了优化的代码,这意味着没有其他考虑,与JavaScript的Clojure进行交流并不容易。莫里是其他考虑因素的层。
>就像Clojure一样,Mori的功能与它们操作的数据结构分开,与JavaScript面向对象的趋势对比。我们会发现这种差异改变了我们编写代码的方向。
莫里还使用结构共享来通过尽可能多的原始结构共享对数据进行有效的更改。这使得持续数据结构几乎与常规瞬态效率一样有效。这些概念的实现在此视频中更详细地介绍了。
><span>// standard library </span><span>Array(1, 2, 3).map(x => x * 2); </span><span>// => [2, 4, 6] </span> <span>// mori </span><span>map(x => x * 2, vector(1, 2, 3)) </span><span>// => [2, 4, 6] </span>
> 首先,让我们想象我们正在尝试在我们继承的JavaScript代码库中追踪一个错误。我们正在阅读代码,试图弄清楚为什么我们最终获得了奖学金的错误价值。
>登录到控制台时奖学金的价值是多少?
>
>不运行代码或阅读deleteperson()的定义,就无法知道。它可能是一个空数组。它可能具有三个新属性。我们希望这是一个删除第二个元素的数组,但是由于我们通过可变的数据结构通过,因此无法保证。<span>const fellowship = [ </span> <span>{ </span> <span>title: 'Mori', </span> <span>race: 'Hobbit' </span> <span>}, </span> <span>{ </span> <span>title: 'Poppin', </span> <span>race: 'Hobbit' </span> <span>} </span><span>]; </span> <span>deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>
将其与Mori的替代方案进行比较。
<span>// transient list </span>a <span>= [1, 2, 3]; </span>b <span>= a.push(4); </span><span>// a = [1, 2, 3, 4] </span><span>// b = [1, 2, 3, 4] </span> <span>// persistent list </span>c <span>= #[1, 2, 3] </span>d <span>= c.push(4); </span><span>// c = #[1, 2, 3] </span><span>// d = #[1, 2, 3, 4] </span>
>不管deleteperson()的实施如何,我们知道原始向量将被记录,仅仅是因为可以保证它不能突变。如果我们希望该函数有用,则应返回并删除指定项目的新向量。
在可变数据上运行的函数并不总是返回值,它们可以突变其输入,有时还留给程序员再次在另一侧拾取值。
更简单,不变的数据可实现可预测性的文化。
>我们将研究如何使用MORI来构建具有撤消功能的像素编辑器。以下代码可作为Codepen可用,您也可以在文章的脚下找到。
>
我们假设您要么跟随Codepen,要么在ES2015环境中使用MORI和以下HTML。
让我们开始通过破坏Mori名称空间所需的功能开始。
<span>// standard library </span><span>Array(1, 2, 3).map(x => x * 2); </span><span>// => [2, 4, 6] </span> <span>// mori </span><span>map(x => x * 2, vector(1, 2, 3)) </span><span>// => [2, 4, 6] </span>
>我们要做的第一件事是设置用于查看持续数据结构的助手功能。莫里(Mori)的内部表示形式在控制台中没有多大意义,因此我们将使用tojs()函数将它们转换为可理解的格式。
>>当我们需要检查Mori的数据结构时,我们可以将此功能用作cons.log()的替代方法。
<span>const fellowship = [ </span> <span>{ </span> <span>title: 'Mori', </span> <span>race: 'Hobbit' </span> <span>}, </span> <span>{ </span> <span>title: 'Poppin', </span> <span>race: 'Hobbit' </span> <span>} </span><span>]; </span> <span>deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>
>希望您注意到我们的to2d()函数返回向量。向量有点像JavaScript数组,并且支持有效的随机访问。
构造数据
<span>import <span>{ vector, hashMap }</span> from 'mori'; </span> <span>const fellowship = vector( </span> <span>hashMap( </span> <span>"name", "Mori", </span> <span>"race", "Hobbit" </span> <span>), </span> <span>hashMap( </span> <span>"name", "Poppin", </span> <span>"race", "Hobbit" </span> <span>) </span><span>) </span> <span>const newFellowship = deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>
>我们使用range()函数来生成一个数字序列在0和高度 *宽度之间(在我们的情况100)之间,并且我们使用map()将其转换为使用to2d的2D坐标列表()辅助功能。
<span><span><span><div</span>></span> </span> <span><span><span><h3</span>></span>Mori Painter<span><span></h3</span>></span> </span><span><span><span></div</span>></span> </span><span><span><span><div</span> id<span>="container"</span>></span> </span> <span><span><span><canvas</span> id<span>='canvas'</span>></span><span><span></canvas</span>></span> </span><span><span><span></div</span>></span> </span><span><span><span><div</span>></span> </span> <span><span><span><button</span> id<span>='undo'</span>></span>↶<span><span></button</span>></span> </span><span><span><span></div</span>></span> </span>
>
这是坐标向量的一维序列。
>在每个坐标的旁边,我们还需要存储一个颜色值。<span>// transient list </span>a <span>= [1, 2, 3]; </span>b <span>= a.push(4); </span><span>// a = [1, 2, 3, 4] </span><span>// b = [1, 2, 3, 4] </span> <span>// persistent list </span>c <span>= #[1, 2, 3] </span>d <span>= c.push(4); </span><span>// c = #[1, 2, 3] </span><span>// d = #[1, 2, 3, 4] </span>
>我们正在使用repot()函数来创建“ #FFF”字符串的无限序列。我们不必担心会填充内存并崩溃我们的浏览器,因为Mori序列支持懒惰评估。我们只在稍后要求时才计算序列中项目的值。
>最后,我们想以哈希地图的形式将坐标与我们的颜色相结合。
<span>// standard library </span><span>Array(1, 2, 3).map(x => x * 2); </span><span>// => [2, 4, 6] </span> <span>// mori </span><span>map(x => x * 2, vector(1, 2, 3)) </span><span>// => [2, 4, 6] </span>
与JavaScript的对象不同,Mori的Hash Maps可以将任何类型的数据作为键。
绘制像素<span>const fellowship = [ </span> <span>{ </span> <span>title: 'Mori', </span> <span>race: 'Hobbit' </span> <span>}, </span> <span>{ </span> <span>title: 'Poppin', </span> <span>race: 'Hobbit' </span> <span>} </span><span>]; </span> <span>deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>
>
>我们使用X和Y坐标来创建可以用作键的坐标向量,然后我们使用Assoc()将该密钥与新颜色相关联。请记住,由于数据结构是持续的,因此assoc.)函数将返回a
newhash映射,而不是突变旧的。
绘画图片<span>import <span>{ vector, hashMap }</span> from 'mori'; </span> <span>const fellowship = vector( </span> <span>hashMap( </span> <span>"name", "Mori", </span> <span>"race", "Hobbit" </span> <span>), </span> <span>hashMap( </span> <span>"name", "Poppin", </span> <span>"race", "Hobbit" </span> <span>) </span><span>) </span> <span>const newFellowship = deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>
>现在,我们有所有需要将简单图像绘制到画布上的一切。让我们创建一个函数,该函数将坐标图的哈希映射针对像素,并将它们绘制到renderingContext2d。 让我们花一点时间了解这里发生了什么。
>>我们正在使用每个()在像素上迭代哈希地图上迭代。它将每个密钥和值(作为序列一起)作为p传递到回调函数中。然后,我们使用inarray()函数将其转换为可能破坏的数组,因此我们可以挑选出所需的值。
>最后,我们使用帆布方法将彩色矩形绘制到上下文本身上。
<span><span><span><div</span>></span> </span> <span><span><span><h3</span>></span>Mori Painter<span><span></h3</span>></span> </span><span><span><span></div</span>></span> </span><span><span><span><div</span> id<span>="container"</span>></span> </span> <span><span><span><canvas</span> id<span>='canvas'</span>></span><span><span></canvas</span>></span> </span><span><span><span></div</span>></span> </span><span><span><span><div</span>></span> </span> <span><span><span><button</span> id<span>='undo'</span>></span>↶<span><span></button</span>></span> </span><span><span><span></div</span>></span> </span>
将其接线
现在,我们需要进行一些管道,以使所有这些部分将所有这些零件一起工作。
<span>const { </span> list<span>, vector, peek, pop, conj, map, assoc, zipmap, </span> range<span>, repeat, each, count, intoArray, toJs </span><span>} = mori; </span>
>
>最后,我们将使用像素通过油漆方法绘制的像素来传递上下文。幸运的是,您的画布应该像白色像素一样渲染。不是最令人兴奋的揭露,但我们越来越接近。<span>const log = (<span>...args</span>) => { </span> <span>console.log(...args.map(toJs)) </span><span>}; </span>
互动
>我们想收听点击事件,并使用它们以较早的draw()函数来更改特定像素的颜色。
><span>// the dimensions of the canvas </span><span>const [height, width] = [20, 20]; </span> <span>// the size of each canvas pixel </span><span>const pixelSize = 10; </span> <span>// converts an integer to a 2d coordinate vector </span><span>const to2D = (i) => vector( </span> i <span>% width, </span> <span>Math.floor(i / width) </span><span>); </span>
>我们将单击侦听器附加到我们的画布上,并使用事件坐标来确定要绘制哪个像素。我们使用此信息使用我们的draw()函数创建新的像素哈希地图。然后,我们将其绘制到我们的上下文中,并覆盖我们画的最后一帧。
在这一点上,我们可以将黑色像素绘制到画布中,每个帧将基于上一个,创建一个复合图像。。
>跟踪帧要实施撤消,我们将需要将每个历史性的修订存储到像素哈希地图上,因此我们将来可以再次检索它们。
我们正在使用列表来存储我们绘制的不同的“帧”。列表支持在头部的有效添加,O(1)查找第一项,这使其非常适合表示堆栈。
<span>// transient list </span>a <span>= [1, 2, 3]; </span>b <span>= a.push(4); </span><span>// a = [1, 2, 3, 4] </span><span>// b = [1, 2, 3, 4] </span> <span>// persistent list </span>c <span>= #[1, 2, 3] </span>d <span>= c.push(4); </span><span>// c = #[1, 2, 3] </span><span>// d = #[1, 2, 3, 4] </span>
我们正在使用PEEK()函数将框架放在堆栈顶部。然后,我们使用它来使用draw()函数创建一个新帧。最后,我们将conj()用于
conjoin
框架顶部的新框架。<span>// standard library </span><span>Array(1, 2, 3).map(x => x * 2); </span><span>// => [2, 4, 6] </span> <span>// mori </span><span>map(x => x * 2, vector(1, 2, 3)) </span><span>// => [2, 4, 6] </span>
尽管我们正在更改本地状态(frame = conj(框架,newFrame)),但我们实际上并未突变任何数据。 撤消更改
>最后,我们需要实现一个撤消按钮,以从堆栈中弹出顶框。>单击“撤消按钮”时,我们检查当前是否有任何帧要撤消,然后使用pop()函数用不再包含顶帧的新列表替换帧。
这是我们最终得到的:
<span>const fellowship = [ </span> <span>{ </span> <span>title: 'Mori', </span> <span>race: 'Hobbit' </span> <span>}, </span> <span>{ </span> <span>title: 'Poppin', </span> <span>race: 'Hobbit' </span> <span>} </span><span>]; </span> <span>deletePerson(fellowship, 1); </span><span>console.log(fellowship); </span>
请参阅codepen上的sitepoint(@sitepoint)的笔莫里像素。
扩展
这是您可以改善此应用程序的方式的想法列表:>
添加一个调色板,允许用户在绘制
之前选择颜色>使用本地存储来保存会话之间的帧
使CTRL Z键盘快捷键撤消更改
允许用户在拖动鼠标时绘制
- 通过移动索引指针来实现重做,而不是从堆栈中删除帧
- >仔细阅读相同程序的clojurescript源
- 结论
- >我们已经研究了向量,列表,范围和哈希地图,但是Mori还附带了集合,排序的集合和队列,这些数据结构中的每一个都带有用于与之合作的多态性功能的补充。
- >我们几乎没有刮过可能的表面,但是希望您能看到足够的时间来重视将持久数据与一组功能强大的简单功能配对的重要性。
经常询问有关不变数据和功能性JavaScript的问题
JavaScript中不变性的概念是什么?这意味着一旦分配了一个值,就无法更改它。这个概念对于功能编程至关重要,因为它有助于避免副作用,并使您的代码更容易预测和易于理解。它还可以通过允许有效的数据检索和内存使用量来提高应用程序的性能。>
> MORI库如何帮助处理JavaScript中的不变数据?持续的数据结构中的JavaScript。这些数据结构是不可变的,这意味着它们一旦创建就无法更改。这有助于保持数据的完整性并避免意外修改。 MORI还提供了一套丰富的功能性编程实用程序,使操纵这些数据结构变得更容易。 >
>使用MORI而不是本机JavaScript方法处理不变数据有什么好处?确实提供了处理不变数据的方法,Mori提供了一种更有效,更健壮的方法。与本机JavaScript方法相比,MORI的持续数据结构更快,并且消耗的内存更少。此外,莫里(Mori 。由于创建一旦创建就无法更改不变的对象,因此可以在多个函数调用中安全地重复使用,而不会被修改。这会导致有效的内存使用和更快的数据检索,从而提高了应用程序的整体性能。>可突变和不可变的数据结构之间有什么区别?> MORI如何处理数据操作?
MORI提供了丰富的功能编程实用程序来操纵数据。这些实用程序允许您在数据结构上执行诸如地图,减少,过滤等的各种操作,而无需更改原始数据。MORI如何确保数据完整性?
MORI通过提供不可变的数据结构来确保数据完整性。由于创建后无法修改这些数据结构,因此消除了意外数据修改的风险。这有助于维持数据的完整性。>
在JavaScript中使用MORI?>
>我如何在我的JavaScript项目中开始使用Mori?在您的JavaScript项目中开始使用Mori,您需要在您的项目中包括Mori库。您可以通过通过NPM安装它或将其直接包含在HTML文件中来执行此操作。包含库后,您可以在代码中开始使用MORI的功能和数据结构。 >
以上是带有MORI的不变数据和功能性JavaScript的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

JavaScript是现代Web开发的基石,它的主要功能包括事件驱动编程、动态内容生成和异步编程。1)事件驱动编程允许网页根据用户操作动态变化。2)动态内容生成使得页面内容可以根据条件调整。3)异步编程确保用户界面不被阻塞。JavaScript广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

不同JavaScript引擎在解析和执行JavaScript代码时,效果会有所不同,因为每个引擎的实现原理和优化策略各有差异。1.词法分析:将源码转换为词法单元。2.语法分析:生成抽象语法树。3.优化和编译:通过JIT编译器生成机器码。4.执行:运行机器码。V8引擎通过即时编译和隐藏类优化,SpiderMonkey使用类型推断系统,导致在相同代码上的性能表现不同。

Python更适合初学者,学习曲线平缓,语法简洁;JavaScript适合前端开发,学习曲线较陡,语法灵活。1.Python语法直观,适用于数据科学和后端开发。2.JavaScript灵活,广泛用于前端和服务器端编程。

JavaScript是现代Web开发的核心语言,因其多样性和灵活性而广泛应用。1)前端开发:通过DOM操作和现代框架(如React、Vue.js、Angular)构建动态网页和单页面应用。2)服务器端开发:Node.js利用非阻塞I/O模型处理高并发和实时应用。3)移动和桌面应用开发:通过ReactNative和Electron实现跨平台开发,提高开发效率。

本文展示了与许可证确保的后端的前端集成,并使用Next.js构建功能性Edtech SaaS应用程序。 前端获取用户权限以控制UI的可见性并确保API要求遵守角色库

从C/C 转向JavaScript需要适应动态类型、垃圾回收和异步编程等特点。1)C/C 是静态类型语言,需手动管理内存,而JavaScript是动态类型,垃圾回收自动处理。2)C/C 需编译成机器码,JavaScript则为解释型语言。3)JavaScript引入闭包、原型链和Promise等概念,增强了灵活性和异步编程能力。

我使用您的日常技术工具构建了功能性的多租户SaaS应用程序(一个Edtech应用程序),您可以做同样的事情。 首先,什么是多租户SaaS应用程序? 多租户SaaS应用程序可让您从唱歌中为多个客户提供服务
