JavaScript Reflect
1. Reflect オブジェクト
Reflect は、JavaScript オブジェクトに対する低レベルな操作を行うためのメソッドを提供するオブジェクトです。
Reflect オブジェクトを使用することで、オブジェクトのプロパティの取得、設定、削除、チェックなどを一貫した方法で実行できます。
Reflect は ES6 (2015) で JavaScript に追加されました。
2. 開発者のツールボックス
Reflect は、オブジェクトを安全かつ一貫した方法で扱うためのツールボックスのような存在です。
2.1 Reflect 以前の状況
Reflect が登場する前、オブジェクト操作の手法はバラバラに散らばっていました:
inやdeleteといった演算子(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)が必要な場合 |
| 新しいオブジェクトの作成 | Yes | Reflect.construct() は Proxy と相性が良いため |
| コンテキストを指定した関数呼び出し | Yes | func.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 も更新される