JavaScript耐性

ハブラカット


免疫とは


不変(英語の不変)は、作成後に状態を変更できないオブジェクトです。 そのようなオブジェクトの変更の結果は常に新しいオブジェクトになりますが、古いオブジェクトは変更されません。


var mutableArr = [1, 2, 3, 4]; arr.push(5); console.log(mutableArr); // [1, 2, 3, 4, 5] //Use seamless-immutable.js var immutableArr = Immutable([1, 2, 3, 4]); var newImmutableArr = immutableArr.concat([5]); console.log(immutableArr); //[1, 2, 3, 4]; console.log(newImmutableArr); //[1, 2, 3, 4, 5]; 

ディープコピーについては説明していません。オブジェクトにネストされた構造がある場合、変更されていないネストされたオブジェクトはすべて再利用されます。


 //Use seamless-immutable.js var state = Immutable({ style : { color : { r : 128, g : 64, b : 32 }, font : { family : 'sans-serif', size : 14 } }, text : 'Example', bounds : { size : { width : 100, height : 200 }, position : { x : 300, y : 400 } } }); var nextState = state.setIn(['style', 'color', 'r'], 99); state.bounds === nextState.bounds; //true state.text === nextState.text; //true state.style.font === state.style.font; //true 

メモリでは、オブジェクトは次のように表されます。


メモリ内


本当ですか? JavaScript不変データ


シンプルで迅速な変更追跡


この機能は、現在人気のあるVirtualDOMReactMithrilRiot )とともに積極的に使用され、Webページの再描画を加速します。


上記のstate例を見てください。 stateオブジェクトを変更した後、それをnextStateオブジェクトと比較し、正確に何が変更されたかを調べる必要があります。 不変性はタスクを大幅に簡素化します。 state埋め込まれた各オブジェクトの各フィールドの値をnextStateの対応する値と比較するnextStateに、対応するオブジェクトへのリンクを単純に比較して、ネストされた比較ブランチ全体を除外できます。


 state === nextState //false state.text === nextState.text //true state.style === nextState.style //false state.style.color === nextState.style.color //false state.style.color.r === nextState.style.color.r //false state.style.color.g === nextState.style.color.g //true state.style.color.b === nextState.style.color.b //true state.style.font === nextState.style.font; //true //state.style.font.family === nextState.style.font.family; //true //state.style.font.size === nextState.style.font.size; //true state.bounds === nextState.bounds //true //state.bounds.size === nextState.bounds.size //true //state.bounds.size.width === nextState.bounds.size.width //true //state.bounds.size.height === nextState.bounds.size.height //true //state.bounds.position === nextState.bounds.position //true //state.bounds.position.x === nextState.bounds.position.x //true //state.bounds.position.y === nextState.bounds.position.y //true 

style.font操作は、不変であり、それらへの参照が変更されていないため、 boundsおよびstyle.fontオブジェクト内では必要ありません。


使いやすく、テストが簡単です。


関数に転送されたデータが誤って破損する可能性があり、そのような状況を追跡することは非常に困難です。


 var arr = [2, 1, 3, 5, 4, 0]; function render(items) { return arr .sort(function(a, b) {return a < b ? -1 : a > b ? 1 : 0}) .map(function(item){ return '<div>' + item + '</div>'; }); } render(arr); console.log(arr); // [0, 1, 2, 3, 4, 5] 

ここで、不変データは状況を保存します。 sort機能は無効になります。


 //Use seamless-immutable.js var arr = [2, 1, 3, 5, 4, 0]; function render(items) { return items .sort(function(a, b) {return a < b ? -1 : a > b ? 1 : 0}) .map(function(item){ return '<div>' + item + '</div>'; }); } render(arr); //Uncaught Error: The sort method cannot be invoked on an Immutable data structure. console.log(arr); 

または、古い配列を変更せずに新しいソートされた配列を返します。


 //Use immutable.js var arr = Immutable.fromJS([2, 1, 3, 5, 4, 0]); function render(items) { return arr .sort(function(a, b) {return a < b ? -1 : a > b ? 1 : 0}) .map(function(item){ return '<div>' + item + '</div>'; }); } render(arr); console.log(arr.toJS()); // [2, 1, 3, 5, 4, 0] 

より大きなメモリ消費


不変オブジェクトが変更されるたびに、必要な変更とともにそのコピーが作成されます。 これにより、通常のオブジェクトを操作する場合よりも多くのメモリが消費されます。 ただし、不変オブジェクトは決して変更されないため、構造共有と呼ばれる戦略を使用して実装できます。これにより、予想よりもはるかに低いメモリコストが生成されます。 組み込みの配列やオブジェクトと比較すると、コストは依然として存在しますが、固定値を持ち、通常は不変性のために利用可能な他の利点によって補うことができます。


キャッシュ(メモ化)が簡単


ほとんどの場合、キャッシュするのは簡単ではありません。 この例は状況を明確にします:


 var step_1 = Immutable({ data : { value : 0 } }); var step_2 = step_1.setIn(['data', 'value'], 1); var step_3 = step_2.setIn(['data', 'value'], 0); step_1.data === step_3.data; //false 

最初のステップのdata.valueは最後のステップのdata.value変わらないという事実にもかかわらず、 dataオブジェクト自体はすでに異なっており、そのリンクも変更されています。


副作用なし


これも事実ではありません。


 function test(immutableData) { var value = immutableData.get('value'); window.title = value; return immutableData.set('value', 42); } 

関数がクリーンになるか、 副作用がないという保証はありません。


コードアクセラレーション。 最適化の余地


ここですべてがそれほど明白ではないため、パフォーマンスは、作業する必要がある不変データ構造の特定の実装に依存します。 ただし、 Object.freezeを使用してオブジェクトを取得し、単にフリーズした場合、そのオブジェクトとそのプロパティへのアクセスは高速にならず、一部のブラウザーではさらに遅くなります。


スレッドセーフ


JavaScriptはシングルスレッドであり、何も話す必要はありません。 多くの人が非同期性とマルチスレッドを混同しています-これは同じことではありません。
デフォルトでは、メッセージキューを非同期で処理するスレッドは1つだけです。
ブラウザにはマルチスレッド用のWebWorkersがありますが、スレッド間で可能な唯一の通信は、文字列またはシリアル化されたJSONを送信することです。 異なるワーカーからの同じ変数にはアクセスできません。


言語機能


キーワードconst


varまたはlet代わりにconstを使用することは、値が定数であること、または不変(不変)であることを意味しません。 constキーワードは、他の値が変数に割り当てられないようにコンパイラーに指示するだけです。


constを使用する場合const最新のJavaScriptエンジンはいくつかの追加の最適化を実行できます。


例:


 const obj = { text : 'test'}; obj.text = 'abc'; obj.color = 'red'; console.log(obj); //Object {text: "abc", color: "red"} obj = {}; //Uncaught TypeError: Assignment to constant variable.(…) 

Object.freeze


Object.freezeメソッドは、オブジェクトをフリーズします。 これは、オブジェクトへの新しいプロパティの追加、オブジェクトからの古いプロパティの削除、および既存のプロパティまたはそれらの列挙、カスタマイズ可能性、および書き込み可能属性の値の変更を防ぐことを意味します。 本質的に、オブジェクトは事実上不変になります。 このメソッドは、凍結されたオブジェクトを返します。


サードパーティのライブラリ


シームレスな不変


このライブラリは、通常の配列およびオブジェクトと下位互換性のある不変のデータ構造を提供します。 つまり、キーまたはインデックスによる値へのアクセスは通常と変わらず、標準サイクルが機能します。また、これはすべて、 LodashUnderscoreなどのデータ操作用の特殊な高性能ライブラリと組み合わせて使用​​できます。


 var array = Immutable(["totally", "immutable", {hammer: "Can't Touch This"}]); array[1] = "I'm going to mutate you!" array[1] // "immutable" array[2].hammer = "hm, surely I can mutate this nested object..." array[2].hammer // "Can't Touch This" for (var index in array) { console.log(array[index]); } // "totally" // "immutable" // { hammer: 'Can't Touch This' } JSON.stringify(array) // '["totally","immutable",{"hammer":"Can't Touch This"}]' 

このライブラリはObject.freeze使用Object.freeze 、データを変更できるメソッドの使用も禁止しています。


  Immutable([3, 1, 4]).sort() // This will throw an ImmutableError, because sort() is a mutating method. 

Safariなどの一部のブラウザーは、 Object.freezeでフリーズしたオブジェクトを操作するときにパフォーマンスの問題があるため、パフォーマンスを向上させるためにproductionアセンブリでこれを無効にします。


Immutable.js


Facebookの進歩により、不変データを操作するためのこのライブラリは、Web開発者の間で最も広く普及し、人気を博しています。 次の不変のデータ構造を提供します。




ClojureScript (リスト、ベクター、マップなど)からJavaScriptに永続的なデータ構造をもたらすライブラリー


Immutable.jsとの違い:



使用例:


 var inc = function(n) { return n+1; }; mori.intoArray(mori.map(inc, mori.vector(1,2,3,4,5))); // => [2,3,4,5,6] //Efficient non-destructive updates! var v1 = mori.vector(1,2,3); var v2 = mori.conj(v1, 4); v1.toString(); // => '[1 2 3]' v2.toString(); // => '[1 2 3 4]' var sum = function(a, b) { return a + b; }; mori.reduce(sum, mori.vector(1, 2, 3, 4)); // => 10 //Lazy sequences! var _ = mori; _.intoArray(_.interpose("foo", _.vector(1, 2, 3, 4))); // => [1, "foo", 2, "foo", 3, "foo", 4] 

直面する開発上の課題


Immutable.jsを使用することです( Moriの場合、すべてがほぼ同じです)。 Seamless-Immutableを使用する場合、ネイティブJavaScript構造との下位互換性により、このような問題は発生しません。


サーバーAPIを操作する


実際、ほとんどの場合、サーバーAPIはJSON形式のデータを受信および返します。これは、JavaScriptの標準オブジェクトおよび配列に対応しています。 これは、不変データを何らかの方法で通常のデータに、またはその逆に変換する必要があることを意味します。


通常のデータを不変に変換するImmutable.jsは、次の機能を提供します。


 Immutable.fromJS(json: any, reviver?: (k: any, v: Iterable<any, any>) => any): any 

reviver関数reviver使用して、独自の変換ルールを追加し、既存のルールを管理できます。


サーバーAPIが次のオブジェクトを返したとします。


 var response = [ {_id : '573b44d91fd2f10100d5f436', value : 1}, {_id : '573dd87b212dc501001950f2', value : 2}, {_id : '5735f6ae2a380401006af05b', value : 3}, {_id : '56bdc2e1cee8b801000ff339', value : 4} ] 

最も便利なのは、そのようなオブジェクトがOrderedMapとして表示されることです。 対応するreviverを作成しreviver


 var state = Immutable.fromJS(response, function(k, v){ if(Immutable.Iterable.isIndexed(v)) { for(var elem of v) { if(!elem.get('_id')) { return elem; } } var ordered = []; for(var elem of v) { ordered.push([elem.get('_id'), elem.get('value')]); } return Immutable.OrderedMap(ordered); } return v; }); console.log(state.toJS()); //Object {573b44d91fd2f10100d5f436: 1, 573dd87b212dc501001950f2: 2, 5735f6ae2a380401006af05b: 3, 56bdc2e1cee8b801000ff339: 4} 

データを変更してサーバーに送り返す必要があるとします。


 state = state.setIn(['573dd87b212dc501001950f2', 5]); console.log(state.toJS()); //Object {573b44d91fd2f10100d5f436: 1, 573dd87b212dc501001950f2: 5, 5735f6ae2a380401006af05b: 3, 56bdc2e1cee8b801000ff339: 4} 

不変データを通常のデータに変換するImmutable.jsには、次の機能があります。


 toJS(): any 

ご覧のとおり、 reviverません。つまり、独自の外部immutableHelperを作成する必要があります。 そして、どういうわけか、彼は通常のOrderMapとソースデータの構造に一致するOrderMapを区別できなければなりません。 また、OrderMapから継承することはできません。 このアプリケーションでは、構造がネストされる可能性が高く、これにより複雑さが増します。


もちろん、開発時にリストとマップのみを使用できますが、それ以外のすべてを使用するのはなぜですか? そして、特にImmutable.jsを使用する利点は何ですか?


どこでも免疫


プロジェクトがネイティブデータ構造を使用していた場合、不変のデータ構造に簡単に切り替えることができます。 データと何らかの形で相互作用するすべてのコードを書き直す必要があります。


シリアライゼーション/デシリアライゼーション


Immutable.jsは、 fromJStoJS fromJS提供します。これらは次のようにtoJSします。


 var set = Immutable.Set([1, 2, 3, 2, 1]); set = Immutable.fromJS(set.toJS()); console.log(Immutable.Set.isSet(set)); //false console.log(Immutable.List.isList(set)); //true 

これは、シリアライゼーション/デシリアライゼーションにはまったく役に立ちません。


サードパーティのtransit-immutable-jsライブラリがあります。 その使用例:


 var transit = require('transit-immutable-js'); var Immutable = require('immutable'); var m = Immutable.Map({with: "Some", data: "In"}); var str = transit.toJSON(m); console.log(str) // ["~#cmap",["with","Some","data","In"]] var m2 = transit.fromJSON(str); console.log(Immutable.is(m, m2));// true 

性能


パフォーマンスをテストするためにベンチマークが作成されました。 それらを自宅で実行するには、次のコマンドを実行します。


 git clone https://github.com/MrCheater/immutable-benchmarks.git cd ./immutable-benchmarks npm install npm start 

ベンチマーク結果はグラフで見ることができます(繰り返し/ ms)。 実行時間が長いほど、結果は悪くなります。


読むとき、ネイティブデータ構造とシームレス不変が最速であることが判明しました。


読む


録音するとき、森は最速であることが判明しました。 シームレス不変は最悪の結果を示しました。


書きます


おわりに


この記事は、パフォーマンスを改善するためにアプリケーションで不変データを使用する必要に直面しているJavaScript開発者に役立ちます。 特に、これは、 VirtualDOMReactMithrilRiot )を使用するフレームワークやFlux / Reduxソリューションを使用するフロントエンド開発者に適用されます。


要約すると、JavaScriptで免疫性が考慮されているライブラリの中で、最も速く、最も便利で使いやすいのはSeamless-immutableです。 最も安定して一般的なのはImmutable.jsです。 記録が最も速く、最も珍しいのはです。 この調査が、プロジェクトのソリューションを選択するのに役立つことを願っています。 がんばって。



Source: https://habr.com/ru/post/J302118/


All Articles