JavaScriptの不変性

不変性は、関数型プログラミングの基本原則であり、オブジェクト指向プログラムにも多くを提供します。 この記事では、不変性の基礎となるもの、JavaScriptでこの概念を使用する方法、およびなぜ有用であるのかを説明します。



不変性とは何ですか?



可変性の本の定義は次のとおりです。「対象の変化または変換の傾向」。 プログラミングでは、状態が時間とともに変化する可能性のあるオブジェクトを意味するときに、この単語を使用します。 不変の値はまったく逆であり、作成後に変更されることはありません。



これが奇妙に思える場合、私たちが常に使用する値のほとんどは実際には不変であることを思い出させてください:

var statement = "I am an immutable value"; var otherStr = statement.slice(8, 17);
      
      





2行目でstatementの文字列値が変更されないことがわかっても、誰も驚かないと思います。 実際には、アクションを実行する行を変更する文字列メソッドはなく、すべて新しい行を返します。 その理由は、行が不変であるためです-それらは変換できず、新しい行のみを作成できます。



JavaScriptに組み込まれている不変の値は文字列だけではありません。 数字も不変です。 式2 + 3の計算によって2 の値変化する環境を想像できますか? これはばかげているように聞こえますが、私たちは常にオブジェクトと配列を使ってやっています。



JavaScriptでは、可変性が豊富です



JavaScriptでは、文字列と数値は不変になるように設計されています。 ただし、配列を使用する次の例を検討してください。

 var arr = []; var v2 = arr.push(2);
      
      





v2の値は何ですか? 配列が文字列と数字に従って動作する場合、v2には1つの要素-2を含む新しい配列が含まれます。ただし、これは別のケースです。 代わりに、リンクarrが数字を含むように変更され、 v2には新しい長さarrが含まれます。



ImmutableArray型を想像してください。 数字と文字列から行動を借りると、次のようになります。

 var arr = new ImmutableArray([1, 2, 3, 4]); var v2 = arr.push(5); arr.toArray(); // [1, 2, 3, 4] v2.toArray(); // [1, 2, 3, 4, 5]
      
      





同様に、ほとんどのオブジェクトの代わりに使用できる不変の連想配列には、実際には何も設定しないプロパティを「設定」するメソッドがありますが、必要な変更を加えた新しいオブジェクトを返します。

 var person = new ImmutableMap({name: "Chris", age: 32}); var olderPerson = person.set("age", 33); person.toObject(); // {name: "Chris", age: 32} olderPerson.toObject(); // {name: "Chris", age: 33}
      
      





2 + 3が2または3の意味を変えないのと同様に、33歳の誕生日を祝う誰かが、その人が32歳前だったという事実をキャンセルしません。



実際のJavaScriptの不変性



JavaScript(これまでのところ)には不変のリストや連想配列がないため、サードパーティのライブラリが必要になりました。 2つの非常に優れたライブラリが利用可能です。 1つ目はMoriで 、ClojureScriptから永続的なデータ構造を適用したり、JavaScriptでAPIをサポートしたりできます。 2番目は、Facebookの開発者が作成したimmutable.jsです。 このデモでは、そのAPIがJavaScript開発者により馴染みがあるという単純な理由でimmutable.jsを使用します。



このデモンストレーションでは、マインスイーパで不変データを操作する原理を検討します。 ボードは、 タイルが最も興味深いデータである不変の連想配列で表されます。 これは、不変の連想配列の不変のリストです。後者のそれぞれ(つまり、連想質量- 約Per。 )は、ボード上の個別のタイルを表します。 コンストラクト全体はJavaScriptオブジェクトと配列を使用して初期化され、 immutable.jsのfromJS関数のおかげで「不滅」になります。

 function createGame(options) { return Immutable.fromJS({ cols: options.cols, rows: options.rows, tiles: initTiles(options.rows, options.cols, options.mines) }); }
      
      





ゲームロジックの残りのコアは、この不変の構造を最初の引数として取り、新しいインスタンスを返す関数として実装されます。 最も重要な関数はRevileTileです。 呼び出されると、彼女はタイルを開くために開いているとマークします。 可変データ構造を使用すると、非常に簡単になります。

 function revealTile(game, tile) { game.tiles[tile].isRevealed = true; }
      
      





しかし、上記で提案したような不変の構造では、複雑になります。

 function revealTile(game, tile) { var updatedTile = game.get('tiles').get(tile).set('isRevealed', true); var updatedTiles = game.get('tiles').set(tile, updatedTile); return game.set('tiles', updatedTiles); }
      
      





妖精! 幸いなことに、そのようなことは珍しくありません。 したがって、私たちのツールキットには、そのような目的のためのメソッドがあります:

 function revealTile(game, tile) { return game.setIn(['tiles', tile, 'isRevealed'], true); }
      
      





renameTile関数は、タイルの1つが前のバージョンと異なる新しい不変のインスタンスを返すようになりました。 setInは null安定であり、キーパーツが存在しない場合は空のオブジェクトで埋められます。 マインスイーパボードの場合、これは望ましくありません。タイルが見つからないということは、ボードの外側でタイルを開こうとしていることを意味します。 これは、 getInを使用して、アクションを実行する前にタイルを検索することで軽減できます。

 function revealTile(game, tile) { return game.getIn(['tiles', tile]) ? game.setIn(['tiles', tile, 'isRevealed'], true) : game; }
      
      





タイルが存在しない場合、既存のゲームを返すだけです。 これは実際には不変性を知っている短い人でした。もっと注意深く理解したい場合は、このcodepenにアクセスしてください 。マインスイーパルールの完全な実装が含まれています。



パフォーマンスはどうですか?



これにより、パフォーマンスが大幅に低下し、何らかの形で正しくなると考えるかもしれません。 不変オブジェクトに何かを追加するたびに、既存の値をコピーして新しい値を追加することにより、新しいインスタンスを作成する必要があります。 これは間違いなく、より多くのメモリ使用率につながり、個々のオブジェクトの突然変異に必要な計算コストよりも大きくなります。



不変オブジェクトは決して変化しないため、構造共有と呼ばれる戦略を使用して実装できます。これにより、予想よりもはるかに低いメモリオーバーヘッドが生成されます。 組み込みの配列やオブジェクトと比較すると、コストは依然として存在しますが、固定値を持ち、通常は不変性により利用可能な他の利点によって補うことができます。 実際には、多くの場合、特定の操作が個別に高価になる場合でも、不変データを使用すると、アプリケーションの全体的なパフォーマンスが向上します。



変更追跡の改善



どのUIフレームワークでも、最大の課題の1つは突然変異を見つけることです。 これは非常によく知られたテストであり、EcmaScript 7は別のAPIを提供して、オブジェクトの突然変異を追跡し、パフォーマンスを向上させます。 このAPIを好む人もいますが、これは間違った質問に対する答えであるように思えます。 いずれにせよ、それは突然変異を適切に追跡する問題を解決しません:

 var tiles = [{id: 0, isRevealed: false}, {id: 1, isRevealed: true}]; Object.observe(tiles, function () { /* ... */ }); tiles[0].id = 2;
      
      





タイル[0]オブジェクトの変異は変異ブラウザをトリガーしません。したがって、変異を追跡するために提案されたメカニズムは、些細なアプリケーションにも適していません。 この状況で不変性はどのように役立ちますか? アプリケーションの状態がaで、潜在的な新しいアプリケーションの状態がbで あると仮定します。

 if (a === b) { //   ,  }
      
      





アプリケーションの状態が変更されていない場合、これは以前と同じインスタンスであり、何もする必要はありません。 このためには、ステートフルリンクを追跡する必要がありますが、今の問題は、単一のリンクを管理する必要があることです。



結論



この記事で、不変性がコードの改善にどのように役立つかについてある程度の知識を得て、継続的な例がこの方向で作業する実際的な側面に光を当てることを願っています。 不変性は人気を集めており、このトピックに関する今年の最後の記事ではありません。 試してみてください。私が気に入ったのと同じくらい早くあなたがそれを好きになると約束します。



All Articles