オブジェクトの話って退屈?
「オブジェクトの話をします」と言われると、多くの人が「ああ、オブジェクトね」と思われる方も多いのではないでしょうか。
私はオブジェクトの機能で言うとObject.assign
でクローンを作ったり、キーを取り出して配列を操作したりするので十分知っていると思っていた時期がありました。
そこで今や古い技術扱いされているjQuery。そのjQueryの使い方から参考になる便利なテクニックがあり、今回はObject.freeze
とObject.seal
について調べてみました。
Object.freezeは文字通り「凍結」
Object.freeze
は、その名の通りオブジェクトを完全に凍らせちゃいます。いったん凍結すると、そのオブジェクトは一切変更できなくなります。
以下のような使い方になります。
let a = {};
Object.freeze(a);
// 値を代入しようとしても..
a.name = "値";
console.log(a); // {}、何も変わっていません!
「それってconst
と同じでは?」と思う方もいらっしゃるかもしれません。
しかし、オブジェクトでは違った挙動をします。
const a = { name: "A" };
a.name = "B"; // これは変更できる
console.log(a); // { name: "B" }
const
は変数の再代入を防ぐだけで、オブジェクトの中身は自由に変更できるんです。でもObject.freeze
を使うと、オブジェクトの中身も一切変更できなくなる。拡張も再定義もできない状態になります。
Object.sealは「封印」する機能
Object.seal
は「封印」です。こちらは凍結より少し制限が緩くて、新しいプロパティの追加はできませんが、既存の値を上書きするのはできます。
const a = Object.seal({ name: "A" });
// 新しいプロパティは追加できない
a.height = 170;
console.log(a.height); // undefined
// でも既存の値は変更できる
a.name = "B";
console.log(a.name); // "B"
実用例
1. ライブラリやモジュール開発が最適
自分で作ったライブラリやモジュールが、他の人によって勝手に再定義されて欲しくない時に使えます。
// Reactを使っているとき
import { useState } from 'react';
// もし誰かがこんなことをしたら...
useState = myCustomUseState;
実際こんなコードを書く人はいないと思いますが、大きなプロジェクトでは似たような名前の関数を誤って上書きしてしまう可能性はありますのでそのような時にはいい仕組みになると思います。
2. サーバーサイドの設定が守れる
サーバーサイドの設定ファイルは、一度読み込んだら変更されてはいけないなものが多いです。
const config = Object.freeze({
port: process.env.PORT,
apiKey: process.env.API_KEY
// ...他の設定
});
これで設定が誤って変更されることを防げます。
3. 単方向データフロー
Reactの内部実装を考えると、ステートストアは本来直接変更されて欲しくないものです。
例えばuseState
を使うときに、以下のような記述をよく書きますよね。
const [a, setA] = useState(0);
このa
は単なるゲッター(値を取得するもの)で、オリジナルの状態はReactの内部ストアに保存されています。もし直接ストアを変更できたら大変なことになります。
なので内部的には状態が凍結されていて、特定のセッター(setA
)を通じてだけ変更できるようになっています。このパターンを実現するのにObject.freeze
のような仕組みが使われています。
これって必要?
「そんなの使わなくてもいいでしょ」と思われる方もいるかもしれませんが、モジュール開発ではこれが本当に役立ちます。
特に大規模プロジェクトになると、人間のエラーがどんどん増えていきます。
コード自体は問題なくても、人間がミスを犯す。プロジェクトが大きすぎて、同じ名前の関数が複数箇所で定義されたりして...
こういう小さな防御策でプロダクトを品質を上げられると個人的には思っています。
おまけ:昔のJSの技
ちょっと昔の話には、プロトタイプ拡張というテクニックがあったようです。
以下のように配列のプロトタイプを拡張してみると
Array.prototype.sum = function() {
return this.reduce((a, b) => a + b, 0);
};
// そうすると、こんな感じで使えます。
[1, 2, 3].sum(); // 6
なんでもオブジェクトになっていて自由に変えられるRubyのようなことができたと言うことです。
大規模なプロジェクトでこんなことをすると危ないので、個人開発でたまに使える感じですかね!
ここで、このようなカスタム拡張をした後はObject.freeze
で保護すれば完璧ですね!