開発者ドキュメント

可変不変JavaScript

私が最初にJavaScriptとプログラミングに飛び込んだとき。 私は不変のデータについて本当に考えたことはありません。 動物はパンダ、それから動物はライオンだと思います。

var animal = 'panda';
animal = 'lion';

私は自分のデータでやりたいことが何でも自由にできました! しかし…状況は変わりました…私は成長しました。 人々は私にこう言い始めました:「可能であれば、常にconstを使用する必要があります」。 だから私は素直にやった。 しかし、私はその理由を本当に理解していませんでした。

不変データを使用する理由

コードが変更されることがあるので、変更したくないものを変更します。 それは私が知っている非常に不完全な答えです、例を挙げてあなたに見せましょう。

eコマースサイトがあるとしましょう。

モジュール:checkout.js
// First we import a function called validate address to check if our users entered a valid address
import validateAddress from 'address-validator'

const checkout = (user, cart) => {
 //... checkout code

 var userAddress = user.address
 // Checking if the address is valid
 const validAddress = validateAddress(userAddress);

 // If the address is valid then
 if (validAddress) {
  //... proceed to checkout
 }
}

npmパッケージをインストールしてアドレスバリデーターを取得したとします。

$ npm install address-validator

すべてが期待どおりに機能しますが、ある日、新しいバージョンがリリースされ、次のような新しいコード行がパッケージに導入されました。

モジュール:validateAddress.js
const validateAddress = (address) => {
 address = '123 My Street, Bring Me Free Goods, USA';
 return true;
}

これで、変数userAddressは常にアドレスの値と等しくなります。 これがどのように問題であるかがわかります。

この特定の問題は、不変性を使用することで解決できます。 ただし、適切なスコープで解決することもできます。 方法を理解してみてください!

もちろん、悪意のあるコードはエッジケースです。 しかし、不変のデータがより良いコードを書くのに役立つ多くの方法があります。 たとえば、非常によくある間違いは、オブジェクトのプロパティを誤って変更することです。

モジュール:accidentical-change.js
const userJack = {name: 'Jack Misteli'};
// I want to make a copy of user to do stuff with it
const user2 = userJack
user2.name = 'Bob'

// Because we didn't do a copy:
// userJack.name === 'bob'

この種の間違いは非常に頻繁に発生する可能性があります。

不変性ツール

最も直感的な不変性ツールは、constを使用することです。

const animal = 'panda';

// This will throw a TypeError!
panda = 'lion';

constは素晴らしいです。 ただし、不変性の錯覚を与えるだけの場合もあります。

モジュール:example-checkout.js
const user = {
 name: 'Jack Misteli',
 address: '233 Paradise Road',
 bankingInfo: 'You wish!'
};

const maliciousAddressValidator = (user) => {
 user.address = 'Malicious Road';
 return true;
};

const validAddress = maliciousAddressValidator(user);
// Now user.address === 'Malicious Road' !!

この問題を解決するにはさまざまな方法があり、不変性を導入することもその1つです。

まず、Object.freezeメソッドを使用できます。

const user = {
 address: '233 Paradise Road'
};
Object.freeze(user)
// Uing the same dodgy validateUserAddress
const validAddress = maliciousAddressValidator(user);
// Now user.address === '233 Paradise Road' !!

Object.freezeの問題の1つは、サブプロパティに影響を与えないことです。 すべてのサブプロパティに到達するには、次のように記述できます。

const superFreeze = (obj) => {
 Object.values(obj).forEach(val =>{
  if (typeof val === 'object')
    superFreeze(val)
  })
  Object.freeze(obj)
}

もう1つの解決策は、柔軟性が必要な場合にプロキシを使用することです。

プロパティ記述子の使用

多くのサイトでは、プロパティ記述子を変更して不変のプロパティを作成できることがわかります。 それは本当ですが、configurablewriteableがfalseに設定されていることを確認する必要があります。

// Setting up a normal getter
const user = {
 get address(){ return '233 Paradise Road' },
};
console.log(Object.getOwnPropertyDescriptor(user, 'address'))
//{
//  get: [Function: get address],
//  set: undefined,
//  enumerable: true,
//  configurable: true
//}

const validAddress = maliciousAddressValidator(user);

// It looks like our data is immutable!
// user.address ===  '233 Paradise Road'

ただし、データは依然として変更可能であり、変更するのはさらに困難です。

const maliciousAddressValidator = (user) => {
 // We don't reassign the value of address directly
 // We reconfigure the address property
  Object.defineProperty(user, "address", {
    get: function() {
     return 'Malicious Road';
   },
 });
};
const validAddress = maliciousAddressValidator(user);
// user.address === 'Malicious Road'

ああ!

書き込み可能で構成可能をfalseに設定すると、次のようになります。

const user = {};
Object.defineProperty(user, "address", {value: 'Paradise Road', writeable: false, configurable: false});


const isValid = maliciousAddressValidator(user)
// This will throw:
// TypeError: Cannot redefine property: address

不変の配列

配列にはオブジェクトと同じ問題があります。

const arr = ['a','b', 'c']
arr[0] = 10
// arr === [ 10, 'b', 'c' ]

上で使用したのと同じツールを使用できます。

Object.freeze(arr)
arr[0] = 10
// arr[0] === 'a'

const zoo = []
Object.defineProperty(zoo, 0, {value: 'panda', writeable: false, configurable: false});
Object.defineProperty(zoo, 1, {value: 'lion', writeable: false, configurable: false});
// zoo === ['panda', 'lion']

zoo[0] = 'alligator'
// The value of zoo[0] hasn't changed
// zoo[0] === 'panda

その他の方法

データを安全に保つための他のトリックがあります。 データを不変にする他の方法については、プロキシトラップに関する記事を確認することを強くお勧めします。 ここでは表面を引っかいただけです。

この投稿では、コードの構造とスタイルを変更せずに不変性を導入するためのオプションについて説明しました。 今後の投稿では、Immutable.js、Immer、Ramdaなどのライブラリを調べて不変のコードを修正します。

モバイルバージョンを終了