不変のJavaScript:ES6以降でそれを行う方法

こんにちは親愛なる読者。 今日は、最新のJavaScriptの不変性に関する記事の翻訳を提供したいと思います。 ES6のさまざまな機能の詳細については、Kyle Simpsonのすばらしい本であるES6などを読むことをお勧めします。



不変のJavascriptコードの記述は正しいです。 Immutable.jsなどの便利なライブラリがいくつかあります。 しかし、今日、ライブラリなしで行うことは可能ですか?「新世代」のバニラJavaScriptで記述してください。



要するに、はい。 ES6およびES.Nextには、面倒なことなく不変の動作を実現できるすばらしい機能がいくつかあります。 この記事では、それらの使用方法を説明します-おもしろいです!



ES.Nextは、EcmaScriptの次のバージョンです。 新しい EcmaScript リリースは毎年リリースされ、 Babelなどのトランスパイラーで現在使用できる機能が含まれています。



問題



まず、不変性がそれほど重要である理由を判断しましょう。 さて、データを変更すると、エラーが発生しやすいコードが読みにくくなる可能性があります。 プリミティブ値(たとえば、数値や文字列)について話している場合、「不変」コードの記述は非常に簡単です。これらの値自体は変更できないためです。 プリミティブ型を含む変数は常に特定の値を示します。 別の変数に渡すと、別の変数がこの値のコピーを受け取ります。



オブジェクト(および配列)の話は異なります:オブジェクトは参照渡しされます 。 これは、オブジェクトを別の変数に渡すと、両方が同じオブジェクトを参照することを意味します。 その後、それらのいずれかに属するオブジェクトを変更すると、変更は両方の変数に影響します。 例:



const person = { name: 'John', age: 28 } const newPerson = person newPerson.age = 30 console.log(newPerson === person) //  console.log(person) // { name: 'John', age: 30 }
      
      





何が起こっているのかわかりますか? newObj



を変更することにより、古いobj



変数も自動的に変更します。 それらは同じオブジェクトを参照するためです。 ほとんどの場合、この動作は望ましくなく、この方法でコードを記述することは不適切です。 この問題を解決する方法を見てみましょう。









不変性を提供します



そして、オブジェクトを転送して変更せずに、 まったく新しいオブジェクトを作成した場合はどうなりますか:



 const person = { name: 'John', age: 28 } const newPerson = Object.assign({}, person, { age: 30 }) console.log(newPerson === person) //  console.log(person) // { name: 'John', age: 28 } console.log(newPerson) // { name: 'John', age: 30 }
      
      





Object.assign



は、オブジェクトをパラメーターとして受け入れることができるES6の機能です。 彼女は、転送されたすべてのオブジェクトを最初のオブジェクトと結合します。 あなたは疑問に思うかもしれません:最初のパラメーターが空のオブジェクト{}



なぜですか? 'person'



パラメーターが最初に来た場合、引き続きperson



変更します。 { age: 30 }



記述した場合、後で30が上書きされるため、値28で再度上書きします。 私たちのソリューションは機能します-私たちは変わらないように行動したので、人は変わりませんでした!



これらの例を試してみてください。 JSBinを開きます 。 左ペインで[Javascript]をクリックし、ES6 / Babelに置き換えます。 すべて、ES6ですでに記述できます:)。



ただし、実際には、EcmaScriptには、このようなタスクをさらに簡素化する特別な構文があります。 これはオブジェクトスプレッドと呼ばれ、Babelトランスポーターで使用できます。 参照:



 const person = { name: 'John', age: 28 } const newPerson = { ...person, age: 30 } console.log(newPerson === person) //  console.log(newPerson) // { name: 'John', age: 30 }
      
      





同じ結果になりましたが、コードはさらにきれいになりました。 まず、 'spread' (...)



演算子は、すべてのプロパティをperson



から新しいオブジェクトにコピーします。 次に、新しい'age'



プロパティを定義し、古いプロパティを上書きします。 順序を維持: person



上にage: 30



が定義されているperson



age: 28



上書きされます。



そして、あなたがアイテムを削除する必要がある場合は? いいえ、この場合はオブジェクトが再び変更されるため、削除しません。 この手法はもう少し複雑で、たとえば次のようにできます。



 const person = { name: 'John', password: '123', age: 28 } const newPerson = Object.keys(person).reduce((obj, key) => { if (key !== property) { return { ...obj, [key]: person[key] } } return obj }, {})
      
      





ご覧のとおり、ほぼすべての操作を個別にプログラムする必要があります。 この機能は、汎用ツールとして最前線に置くことができます。 しかし、配列の変更と不変性はどのように適用されますか?



配列



小さな例:要素を変更して配列に追加する方法:



 const characters = [ 'Obi-Wan', 'Vader' ] const newCharacters = characters newCharacters.push('Luke') console.log(characters === newCharacters) //  :-(
      
      





オブジェクトと同じ問題。 新しいアレイの作成に失敗しました。古いアレイを変更しました。 幸いなことに、ES6にはアレイのスプレッドステートメントがあります! 使用方法は次のとおりです。



 const characters = [ 'Obi-Wan', 'Vader' ] const newCharacters = [ ...characters, 'Luke' ] console.log(characters === newCharacters) // false console.log(characters) // [ 'Obi-Wan', 'Vader' ] console.log(newCharacters) // [ 'Obi-Wan', 'Vader', 'Luke' ]
      
      





とても簡単です! 古い文字と「Luke」を含む新しい配列を作成しましたが、古い配列は変更されませんでした。



元の配列を変更せずに、配列を使用して他の操作を行う方法を検討してください。



 const characters = [ 'Obi-Wan', 'Vader', 'Luke' ] //   const withoutVader = characters.filter(char => char !== 'Vader') console.log(withoutVader) // [ 'Obi-Wan', 'Luke' ] //     const backInTime = characters.map(char => char === 'Vader' ? 'Anakin' : char) console.log(backInTime) // [ 'Obi-Wan', 'Anakin', 'Luke' ] //      const shoutOut = characters.map(char => char.toUpperCase()) console.log(shoutOut) // [ 'OBI-WAN', 'VADER', 'LUKE' ] //     const otherCharacters = [ 'Yoda', 'Finn' ] const moreCharacters = [ ...characters, ...otherCharacters ] console.log(moreCharacters) // [ 'Obi-Wan', 'Vader', 'Luke', 'Yoda', 'Finn' ]
      
      





どのような「機能的な」演算子を参照してくださいか? ES6の矢印関数の構文は、それらに色を付けるだけです。 そのような関数が起動されるたびに、そのような関数は新しい配列を返しますが、1つの例外は古代のソート方法です。



 const characters = [ 'Obi-Wan', 'Vader', 'Luke' ] const sortedCharacters = characters.sort() console.log(sortedCharacters === characters) //  :-( console.log(characters) // [ 'Luke', 'Obi-Wan', 'Vader' ]
      
      





はい、知っています。 push



sort



は、新しい配列を返すためにmap



filter



concat



とまったく同じように動作するはずだと思います。 しかし、彼らはそうしません、そして、あなたが何かを変えるならば、あなたはおそらくインターネットを壊すことができます。 並べ替えが必要な場合は、 slice



を使用して機能させることができます。



 const characters = [ 'Obi-Wan', 'Vader', 'Luke' ] const sortedCharacters = characters.slice().sort() console.log(sortedCharacters === characters) // false :-D console.log(sortedCharacters) // [ 'Luke', 'Obi-Wan', 'Vader' ] console.log(characters) // [ 'Obi-Wan', 'Vader', 'Luke' ]
      
      





slice()



はちょっとしたハックのように感じslice()



が、動作します。

ご覧のとおり、不変性は最も一般的な最新のJavaScriptを使用して簡単に実現できます。 最後に、最も重要なことは常識であり、コードが正確に何をするかを理解することです。 誤ってプログラミングすると、JavaScriptが予測不能になる可能性があります。



パフォーマンスノート



パフォーマンスはどうですか? 結局のところ、新しいオブジェクトを作成することは時間とメモリの浪費ですか? はい、確かに、追加費用がかかります。 しかし、この欠点は、獲得した利点によって補われる以上のものです。



JavaScriptで最も複雑な操作の1つは、オブジェクトの変更の追跡です。 Object.observe(object, callback)



ようなソリューションObject.observe(object, callback)



かなりObject.observe(object, callback)



です。 ただし、状態を変更せずに維持する場合は、 oldObject === newObject



取得でき、オブジェクトが変更されたかどうかを確認できます。 この操作はCPUにそれほど負荷をかけません。



2番目の重要な利点は、コードの品質が向上することです。 状態の不変性を保証する必要がある場合は、アプリケーション全体の構造をよく検討する必要があります。 「より機能的に」プログラムし、コード全体を追跡しやすくし、その中の悪名高いバグをあまり頻繁に巻き上げません。 どこでもあなたが投げる-どこでもワイン、右?



参照用



» ES6互換性テーブル:

» ES.Next互換性テーブル:



All Articles