JavaScript アドバンス

JavaScript Reflect

1. Reflect オブジェクト

Reflect は、JavaScript オブジェクトに対する低レベルな操作を行うためのメソッドを提供するオブジェクトです。
Reflect オブジェクトを使用することで、オブジェクトのプロパティの取得、設定、削除、チェックなどを一貫した方法で実行できます。

Reflect は ES6 (2015) で JavaScript に追加されました。

2. 開発者のツールボックス

Reflect は、オブジェクトを安全かつ一貫した方法で扱うためのツールボックスのような存在です。

2.1 Reflect 以前の状況

Reflect が登場する前、オブジェクト操作の手法はバラバラに散らばっていました:

  • indelete といった演算子(Operator)の使用
  • Object.defineProperty といったメソッドの使用
  • [[Get]][[Set]] といった言語内部のメカニズムの使用

2.2 Reflect 以降の状況

Reflect は、これらすべてのオブジェクト操作をクリーンなメソッドへと統合しました:

  • オブジェクト操作のメソッドを統一
  • 演算子(in / delete)よりも予測可能性が高い
  • エラーを投げる代わりに、標準的な戻り値(true/false)を返す
  • メタプログラミングにおいて、よりクリーンで安全
  • Proxy オブジェクトのハンドラー(トラップ)とシームレスに連携するように設計されている

3. 具体的な例

Reflect.has() を使えば、in 演算子を関数として利用できます。
Reflect.deleteProperty() を使えば、delete 演算子を関数として利用できます。

Reflect は、特に Proxy の内部で使用する場合に、その安全性と柔軟性を発揮します。

4. Reflect.has()

Reflect.has() メソッドは、オブジェクトが特定のプロパティを持っているかどうかをチェックします。
これは in 演算子と似た動作をします。

4.1 プロパティの存在確認

// オブジェクトを作成
const person = {name: "John", lastname: "Doe"};

let answer = Reflect.has(person, "name");

これは、以下の in 演算子の使用と同じです:

let answer = "name" in person;
項目内容
構文Reflect.has(obj, prop)
引数obj - ターゲットオブジェクト
prop - チェックするプロパティ名
戻り値存在すれば true、なければ false
例外obj がオブジェクトでない場合、TypeError がスローされる

5. Reflect.deleteProperty()

Reflect.deleteProperty() メソッドは、オブジェクトからプロパティを削除します。
これは delete 演算子と似た動作をします。

5.1 プロパティの削除

// オブジェクトを作成
const person = {name: "John", lastname: "Doe"};

Reflect.deleteProperty(person, "name");

これは、以下の delete 演算子の使用と同じです:

delete person.name;
項目内容
構文Reflect.deleteProperty(obj, prop)
引数obj - ターゲットオブジェクト
prop - 削除するプロパティ名
戻り値削除に成功すれば true、失敗すれば false
例外obj がオブジェクトでない場合、TypeError がスローされる

6. Reflect.get()

Reflect.get() メソッドは、プロパティの値を取得します。

// オブジェクトを作成
const user = {name: "Jan", age: 40};

let age = Reflect.get(user, "age");

これは、以下と同じです:

let age = user.age;
項目内容
構文Reflect.get(obj, prop [, receiver])
引数obj - ターゲットオブジェクト
prop - 取得するプロパティ名
receiver - ゲッター(getter)の場合の this 値(任意)
戻り値プロパティの値
例外obj がオブジェクトでない場合、TypeError がスローされる

7. Reflect.set()

Reflect.set() メソッドは、プロパティの値を設定します。

// オブジェクトを作成
const user = {name: "Jan", age: 40};

Reflect.set(user, "age", 41);

// 値の確認
let age = Reflect.get(user, "age");

これは、以下と同じです:

user.age = 41;
項目内容
構文Reflect.set(obj, prop, value [, receiver])
引数obj - ターゲットオブジェクト
prop - 設定するプロパティ名
value - 設定する値
receiver - セッター(setter)の場合の this 値(任意)
戻り値成功すれば true、失敗すれば false
例外obj がオブジェクトでない場合、TypeError がスローされる

8. Reflect.apply()

Reflect.apply() メソッドは、指定された this 引数と配列(または配列風オブジェクト)の引数で関数を呼び出します。

function greet(message) {
  return message + ", " + this.name;
}

const person = {name: "Jan"};

let msg = Reflect.apply(greet, person, ["こんにちは"]);

これは、greet.apply() を使用するのと同じです:

let msg = greet.apply(person, ["こんにちは"]);
項目内容
構文Reflect.apply(targetFunction, thisArgument, argumentsList)
引数targetFunction - 呼び出す関数
thisArgument - 呼び出し時の this 値
argumentsList - 関数の引数リスト(配列風オブジェクト)
戻り値関数の実行結果
例外第1引数が関数でない、または第3引数がオブジェクトでない場合、TypeError

Reflect.apply() は以下のような場合に推奨されます:

  • メタプログラミングを行っているとき
  • Proxy のハンドラー内であるとき
  • 一貫した動作を求めるとき(暗黙的なエラーを避けたい場合)
  • JavaScript の内部操作をミラーリングしたいとき

9. Reflect.construct()

Reflect.construct() メソッドは new 演算子のように動作し、指定された引数でターゲットの新しいインスタンスを作成します。また、newTarget を指定することで、サブクラス化のためのカスタムな new.target 値を設定できます。

// 新しい配列(Array)を作成
const colors = Reflect.construct(Array, ["赤", "緑", "青"]);

これは、new キーワードを使用するのと同じです:

const colors = new Array("赤", "緑", "青");

10. Reflect.defineProperty()

Reflect.defineProperty() は、プロパティを定義または修正します。

// オブジェクトを作成
const user = {};

// プロパティを追加
Reflect.defineProperty(user, "id", {
  value: 123,
  writable: false
});

これは、Object.defineProperty() を使用するのと同じです。

10.1 Proxy における重要性

Proxy の中で使用する場合、defineProperty トラップは必ず true または false を返さなければなりません。

  • Object.defineProperty() はターゲットオブジェクトを返します。
  • Reflect.defineProperty()成功か失敗かの真偽値(boolean)を返します。 このため、Proxy 内部では Reflect 版を使用するのが標準的です。

11. Reflect.ownKeys()

Reflect.ownKeys(obj) メソッドは、オブジェクト自身のプロパティキー(文字列と Symbol の両方)の配列を返します。これは Object.getOwnPropertyNames()Object.getOwnPropertySymbols() を組み合わせたような動作です。

const sym = Symbol("秘密");
const obj = { a: 1, [sym]: 2 };

let keys = Reflect.ownKeys(obj); // ["a", Symbol(秘密)]

11.1 なぜ Reflect を使うのか?

Reflect.ownKeys()Symbol ベースのキーも返しますが、Object.keys() は Symbol を返しません。

12. Reflect.isExtensible()

Reflect.isExtensible() メソッドは、オブジェクトが拡張可能(新しいプロパティを追加できる)かどうかをチェックします。

12.1 なぜ Reflect を使うのか?

  • Object.isExtensible() はユーザー向けのメソッドであり、内部的なトラップ機構の一部ではありません。
  • Reflect.isExtensible() は、内部メソッド [[IsExtensible]] に直接マッピングされています。
  • また、プリミティブなターゲットに対する挙動が異なります。
Reflect.isExtensible(1); // TypeError をスロー
Object.isExtensible(1);  // false を返す(エラーにならない)

13. Reflect を使用すべきケース

ケース使用の有無理由
値の取得・設定Yes一貫した戻り値(true/false)が必要な場合
新しいオブジェクトの作成YesReflect.construct() は Proxy と相性が良いため
コンテキストを指定した関数呼び出しYesfunc.apply() よりも Reflect.apply() の方がクリーン
メタプログラミングYes低レベルなタスク向けに設計されているため
単純なオブジェクト操作No通常の JavaScript 構文(.=)を使用すべき

14. Reflect と Proxy の連携(非常に一般的)

Proxy を使用すると、オブジェクトに対する操作をインターセプト(割り込み)できます。Reflect を併用することで、インターセプトした操作を安全に元のオブジェクトへ「転送(Forwarding)」できます。

実装例:

// オブジェクトを作成
const user = { name: "Jan", age: 40 };

// Proxy を作成
const proxy = new Proxy(user, {
  set(target, property, value) {
    console.log(property + ": " + value);
    // 安全な転送(本来のセット操作を実行)
    return Reflect.set(target, property, value);
  }
});

proxy.age = 41; // ログが出力され、user.age も更新される