ホームページ > ウェブフロントエンド > jsチュートリアル > Moriを使用した不変のデータと機能的JavaScript

Moriを使用した不変のデータと機能的JavaScript

Joseph Gordon-Levitt
リリース: 2025-02-18 08:34:10
オリジナル
720 人が閲覧しました

Moriを使用した不変のデータと機能的JavaScript

キーテイクアウト

  • moriは、Clojureの永続的なデータ構造を活用し、JavaScript開発者にコードのシンプルさと信頼性を高める不変のデータオプションを提供します。
  • 森の使用は、不変性を強制することにより、JavaScriptの機能的なプログラミングパラダイムを促進し、意図しない副作用を防ぎ、アプリケーションライフサイクル全体でデータの一貫性を確保します。
  • ライブラリは、データがデータ構造で個別に動作するデータを処理するための異なるアプローチを促進し、JavaScriptの典型的なオブジェクト指向の方法とは対照的であり、よりクリーンで予測可能なコードを可能にします。
  • 森の構造共有手法により、可能な限り既存のデータ構造を再利用することにより、データ操作を効率的にし、アプリケーションのパフォーマンスの改善につながる可能性があります。
  • 機能を備えたPixelエディターのような例を通じて、MORIは不変のデータ構造の実用的なアプリケーションを実証し、開発者に洗練された堅牢な機能を構築するツールを提供します。
  • この記事は、クレイグ・ビルナーとエイドリアン・サンドゥによって査読されました。 SetePointコンテンツを最高にするためにSitePointのピアレビュアーのすべてに感謝します!
  • 機能的プログラミングと不変のデータは、コードをよりシンプルで推論しやすくする方法を見つけようとする多くのJavaScript開発者にとって現在の焦点です。
  • 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は、ClojureScriptコンパイラを使用して、Clojureの標準ライブラリのデータ構造の実装をJavaScriptにコンパイルします。コンパイラは最適化されたコードを発します。つまり、追加の考慮がなくても、JavaScriptのコンパイルされたClojureと通信するのは簡単ではありません。森は、追加の考慮事項の層です。

Clojureと同様に、Moriの機能は、操作しているデータ構造から分離されており、JavaScriptのオブジェクト指向の傾向とは対照的です。この違いがコードを書く方向が変わることがわかります。

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>
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

コンソールにログに記録されたフェローシップの価値は何ですか?

コードを実行したり、deleteperson()の定義を読んでも、知る方法はありません。空の配列である可能性があります。 3つの新しいプロパティがあります。 2番目の要素が削除されたアレイであることを願っていますが、可変データ構造に合格したため、保証はありません。 さらに悪いことに、この機能は参照を保持し、将来非同期に変異させる可能性があります。ここからのフェローシップへのすべての言及は、予測不可能な価値で作業する予定です。

これを森の代替案と比較します。

<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を使用した不変のデータと機能的JavaScript関数は常に値を返すわけではなく、入力を変異させることができ、時にはプログラマに任せて反対側で再び値を拾うことができます。

より簡単に言えば、不変のデータは予測可能性の文化を強制します

実際にはMoriを使用した不変のデータと機能的JavaScript

森を使用して、機能性を備えたピクセルエディターを構築する方法を検討します。次のコードは、記事のふもとでも見つけることができるCodepenとして入手できます。

Codepenをフォローしているか、MORIと次のHTMLを使用してES2015環境で作業していると仮定します。

セットアップ&ユーティリティMoriを使用した不変のデータと機能的JavaScript

森の名前空間から必要な機能を破壊することから始めましょう。

これは主に文体的な好みです。 Moriオブジェクト(例:mori.list())に直接アクセスすることで、MORIの関数を使用することもできます。

最初に行うことは、永続的なデータ構造を表示するためのヘルパー関数をセットアップすることです。森の内部表現はコンソールではあまり意味がないため、tojs()関数を使用して理解可能な形式に変換します。
<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>
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

森のデータ構造を検査する必要がある場合、console.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アレイに少し似ており、効率的なランダムアクセスをサポートしています。

データの構造

to2d()関数を使用して、キャンバス上のすべてのピクセルを表す一連の座標を作成します。

<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>
ログイン後にコピー
ログイン後にコピー
これは、座標ベクトルの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>
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

repeat()関数を使用して、「#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>
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

ZIPMAP()関数を使用してハッシュマップを作成し、キーとしての座標と色を値として使用しています。繰り返しますが、データの構造を視覚化するのに役立つかもしれません。

<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>
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
JavaScriptのオブジェクトとは異なり、Moriのハッシュマップは、あらゆる種類のデータをキーとして取得できます。

ピクセル

を描画します

ピクセルの色を変更するには、ハッシュマップの座標の1つを新しい文字列に関連付けます。単一のピクセルを色付けする純粋な関数を書きましょう。

xおよびy座標を使用して、キーとして使用できる座標ベクトルを作成し、そのキーを新しい色に関連付けるためにassoc()を使用します。データ構造は永続的であるため、Assoc()関数は古いマップを変異させるのではなく、

new
<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に描画する関数を作成しましょう。

ここで何が起こっているのかを理解しましょう。

各()を使用して、ピクセルのハッシュマップを反復します。各キーと値(シーケンスとして一緒に)をpaspとしてコールバック関数に渡します。次に、IntherArray()関数を使用して、それを破壊できる配列に変換するため、必要な値を選択できます。

<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>
ログイン後にコピー
ログイン後にコピー
最後に、Canvasメソッドを使用して、コンテキスト自体に色付きの長方形を描画します。

一緒に配線する

ここで、これらすべての部品をまとめて動作させるためだけに、少し配管する必要があります。
<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()関数を使用して新しいピクセルハッシュマップを作成します。それから私たちはそれを私たちのコンテキストに塗り、描いた最後のフレームを上書きします。

この時点で、黒いピクセルをキャンバスに描くことができ、各フレームは前のフレームに基づいて、複合画像を作成します。

トラッキングフレーム

元に戻すには、各歴史的な改訂版をピクセルハッシュマップに保存して、将来再び取得できるようにする必要があります。

<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>
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
リストを使用して、描画したさまざまな「フレーム」を保存しています。リストは、最初のアイテムの頭とO(1)の検索をサポートします。これにより、スタックを表現するのに最適です。

クリックリスナーを変更して、フレームスタックを使用して作業する必要があります。

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(frames、newFrame))を変更していますが、実際にデータを変異させていません。 変更を取り消す

最後に、スタックからトップフレームをポップするために元に戻すボタンを実装する必要があります。

元に戻すボタンをクリックしたら、元に戻すフレームがあるかどうかを確認し、POP()関数を使用して、フレームを上部フレームを含む新しいリストに置き換えます。

最後に、変更を反映するために、新しいスタックの上部フレームをpaint()関数に渡します。この時点で、キャンバスの変更を描画して元に戻すことができるはずです。

demo
<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の不変性の概念は何ですか?

    ​​

    JavaScriptの不変性は、作成後に変更できないオブジェクトの状態を指します。これは、変数に値が割り当てられたら、変更できないことを意味します。この概念は、副作用を回避し、コードをより予測可能で理解しやすくするため、機能的なプログラミングにおいて重要です。また、効率的なデータ検索とメモリの使用を許可することにより、アプリケーションのパフォーマンスを向上させます。 JavaScriptへの永続的なデータ構造。これらのデータ構造は不変です。つまり、作成されると変更できません。これは、データの完全性を維持するのに役立ち、偶発的な変更を回避します。 Moriは、これらのデータ構造を操作しやすくする機能的プログラミングユーティリティの豊富なセットも提供します。不変のデータを処理する方法を提供している森は、より効率的で堅牢な方法を提供します。森の永続的なデータ構造は、ネイティブのJavaScriptメソッドよりも速く、消費量が少ないメモリを消費します。さらに、MoriはJavaScriptで利用できない幅広い機能プログラミングユーティリティを提供します。 。不変のオブジェクトを作成すると変更できないため、変更されるリスクなしに複数の機能呼び出しで安全に再利用できます。これにより、効率的なメモリ使用量とデータの検索が速くなり、アプリケーションの全体的なパフォーマンスが向上します。作成後に変更されます。一方、不変のデータ構造は作成されると変更できません。不変のデータ構造の操作は、新しいデータ構造をもたらします。

    MORIはデータの操作をどのように処理しますか?

    MORIは、データを操作するための機能的なプログラミングユーティリティの豊富なセットを提供します。これらのユーティリティを使用すると、元のデータを変更せずにデータ構造でマップ、削減、フィルターなど、さまざまな操作を実行できます。森には、変更されたときに以前のバージョンのデータを保存する不変のデータ構造があります。これは、永続的なデータ構造で操作を実行するたびに、データ構造の新しいバージョンが作成され、古いバージョンが保持されることを意味します。

    MORIは、データの整合性をどのように保証しますか?

    Moriは、不変のデータ構造を提供することにより、データの整合性を保証します。これらのデータ構造を作成すると変更できないため、偶発的なデータ変更のリスクは排除されます。これは、データの整合性を維持するのに役立ちます。

    JavaScriptを使用したJavaScriptの機能的プログラミングの利点は何ですか?副作用を回避することで、コードがより予測可能で理解しやすくなります。また、効率的なデータ取得とメモリ使用量を許可することにより、アプリケーションのパフォーマンスを向上させます。プロジェクトに森ライブラリを含める必要があります。これを行うには、NPM経由でインストールするか、HTMLファイルに直接含めることができます。ライブラリが含まれたら、コード内のMoriの機能とデータ構造の使用を開始できます。

以上がMoriを使用した不変のデータと機能的JavaScriptの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート