JavaScript | ES6+ 文法:その他の ES6+ 機能 – Reflect

JavaScript JavaScript
スポンサーリンク

Reflect とは何か(まずイメージから)

Reflect は ES6 で追加された、
「オブジェクトに対する基本操作を、“関数の形”でまとめて持っている道具箱」 です。

これまでバラバラに存在していた

  • プロパティの取得 / 設定
  • プロパティの削除
  • in 演算子による存在確認
  • Object.defineProperty などのメタ操作

といったものを、Reflect.get, Reflect.set, Reflect.deleteProperty …のように
一つのオブジェクトに整理して置いてくれた、という位置づけです。

そして、Proxy と組み合わせるときにほぼ必ず登場する相棒でもあります。

ここが重要です。
Reflect は「新しい魔法」ではなく、
「今まであったことを “きれいに・一貫した形で” やり直した API 集合」 だと思うとスッと入ってきます。


従来の書き方 vs Reflect の書き方

まずは、「何が変わるの?」をコードでざっくり見てみます。

プロパティの読み書き

今まで:

const user = { name: "Alice" };

// 読み取り
console.log(user.name);

// 書き込み
user.name = "Bob";
JavaScript

Reflect を使うと:

const user = { name: "Alice" };

// 読み取り
console.log(Reflect.get(user, "name"));  // "Alice"

// 書き込み
Reflect.set(user, "name", "Bob");
console.log(user.name); // "Bob"
JavaScript

見た目は少し遠回りですが、
「操作を全部関数として扱える」という意味で、Reflect 版にも価値があります。

プロパティの削除

今まで:

delete user.name;
JavaScript

Reflect:

Reflect.deleteProperty(user, "name");
JavaScript

プロパティの存在確認(in の代わり)

今まで:

console.log("name" in user); // true / false
JavaScript

Reflect:

console.log(Reflect.has(user, "name"));
JavaScript

プロパティ定義(Object.defineProperty の代わり)

今まで:

Object.defineProperty(user, "age", {
  value: 20,
  writable: true,
  enumerable: true,
  configurable: true,
});
JavaScript

Reflect:

Reflect.defineProperty(user, "age", {
  value: 20,
  writable: true,
  enumerable: true,
  configurable: true,
});
JavaScript

このあたりは見た目はほぼ同じですが、
「成功 / 失敗の返り値」などの挙動が整理されています(後で少し触れます)。

ここが重要です。
Reflect は「できること」を増やすというより、
「既にある操作を、統一された関数 API として提供し直した」 というイメージです。


なぜ Reflect がわざわざ作られたのか

1. 「関数として呼びたい」場面があるから

例えば、Proxy の trap(get, set など)から「本来の動作」を呼びたいとき、
target[prop] と書くこともできますが、
毎回「this や受け取り方」を気にするのは面倒です。

Reflect を使えば、「オブジェクトへの基本操作」が全部関数なので、
そのまま呼び出せます。

const handler = {
  get(target, prop, receiver) {
    console.log("読み取り:", prop);
    return Reflect.get(target, prop, receiver);  // 素の挙動に“委譲”
  },
};
JavaScript

Proxy で横取りするけど、一部は標準の動きに任せたい、
というときに Reflect がとても自然に使えます。

2. 戻り値の仕様をそろえたかった

例えば Object.defineProperty

失敗したときの挙動が少しややこしく、

  • 成功 → オブジェクトを返す
  • 失敗 → 例外を投げる

という形でした。

Reflect 版の Reflect.defineProperty は、
成功 / 失敗を true / false の返り値で返します。

const obj = {};

const ok = Reflect.defineProperty(obj, "x", { value: 1 });
console.log(ok); // true(成功)

// 失敗するケースでは false が返る(例外ではなく)
JavaScript

「例外」を使いたくない場面や、
「とりあえず試してダメだったら別の処理へ」というロジックを組みたいときに、
true / false のほうが扱いやすいことがあります。

同じことが Reflect.setReflect.deleteProperty にも当てはまります。
成功したかどうかが boolean で返ってきます。

3. 「メタプログラミング用の場所」としてまとめたかった

これまで、メタっぽい操作はあちこちに散らばっていました。

  • Object.defineProperty, Object.getOwnPropertyDescriptor
  • in 演算子
  • delete 演算子
  • Function.prototype.apply など

それを「メタプロっぽい操作は Reflect に集約しよう」という整理の意味もあります。

ここが重要です。
Reflect は、Proxy とセットで使うための「素の挙動を表す標準 API」 としての役割がとても大きいです。
Proxy の trap の中で Reflect.xxx を呼ぶ、という形が非常に多く登場します。


よく使う Reflect メソッドと具体例

初心者のうちに「これだけ知っておくといい」というものに絞って説明します。

Reflect.get:プロパティの取得(通常の obj[prop] の関数版)

const user = { name: "Alice", age: 20 };

console.log(Reflect.get(user, "name"));  // "Alice"
console.log(Reflect.get(user, "age"));   // 20
JavaScript

第 3 引数に receiver を渡すと、
ゲッター(getter)がある場合の this をコントロールできますが、
最初のうちは意識しなくても大丈夫です。

Reflect.set:プロパティの設定(obj[prop] = value の関数版)

const user = { name: "Alice" };

const ok = Reflect.set(user, "name", "Bob");

console.log(ok);        // true(成功したかどうか)
console.log(user.name); // "Bob"
JavaScript

Reflect.has:in 演算子の関数版

const user = { name: "Alice" };

console.log(Reflect.has(user, "name"));     // true
console.log(Reflect.has(user, "age"));      // false
console.log("name" in user);                // 同じ結果
JavaScript

Reflect.deleteProperty:delete obj[prop] の関数版

const user = { name: "Alice", age: 20 };

const deleted = Reflect.deleteProperty(user, "age");
console.log(deleted);  // true
console.log(user.age); // undefined
JavaScript

Reflect.ownKeys:すべての自プロパティキーを取得

Object.keys は列挙可能な文字列キーだけを返しますが、
Reflect.ownKeys は Symbol を含むすべての自プロパティキーを返します。

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

console.log(Object.keys(obj));     // ["a"]
console.log(Reflect.ownKeys(obj)); // ["a", Symbol(id)]
JavaScript

Reflect.apply:関数を指定した this / 引数で呼び出す

func.apply(thisArg, argsArray) の Reflect 版です。

function sum(a, b) {
  return a + b;
}

const result = Reflect.apply(sum, null, [1, 2]);
console.log(result); // 3
JavaScript

Proxy の apply trap 内で、「元の関数をそのまま呼びたい」場合などに使います。


Proxy と Reflect の組み合わせ(ここが一番おいしい)

Reflect が一気に「必要なやつ」に見えてくるのは、Proxy と合わせて使ったときです。

例:get を横取りしてログを出しつつ、本来の挙動は保つ

const user = { name: "Alice", age: 20 };

const handler = {
  get(target, prop, receiver) {
    console.log("アクセス:", prop);
    return Reflect.get(target, prop, receiver); // 本来の get を呼ぶ
  },
};

const proxy = new Proxy(user, handler);

console.log(proxy.name); // ログ: アクセス: name → "Alice"
console.log(proxy.age);  // ログ: アクセス: age  → 20
JavaScript

もしここで Reflect.get を使わないと、
target[prop] としか書けませんが、
ゲッターや継承などの「細かい仕様」を正しく再現するのが難しくなります。

Reflect.get は、「言語仕様的に正しい get の挙動」をまとめて引き受けてくれるので、
Proxy の中で素直に「標準の動きに任せる」ことができます。

例:set でバリデーション+本来の set を呼ぶ

const user = { name: "Alice", age: 20 };

const handler = {
  set(target, prop, value, receiver) {
    console.log(`set: ${String(prop)} = ${value}`);

    if (prop === "age" && (typeof value !== "number" || value < 0)) {
      console.log("不正な age の値です");
      return false;
    }

    return Reflect.set(target, prop, value, receiver);
  },
};

const proxy = new Proxy(user, handler);

proxy.age = 30;  // OK, set: age = 30
proxy.age = -1;  // 不正な age の値です
JavaScript

ここでも、Reflect.set を使うことで
「基本の動き」は Reflect に任せ、
自分はバリデーションやログに集中できます。

ここが重要です。
Proxy の役割:操作を横取りする。
Reflect の役割:横取りした後で「元の正しい操作」を実行するための道具。

このコンビを理解すると、
「Proxy の handler の中では、とりあえず Reflect.xxx を呼んでおけば、基本挙動は壊さない」
という安心感が生まれます。


初心者は Reflect をどこまで理解しておくといいか

最低限のライン

今の段階で押さえておけば十分、というラインはここです。

Reflect は「オブジェクト操作の標準 API 集合」で、Reflect.get / set / has / deleteProperty などがある。

従来の obj[prop], prop in obj, delete obj[prop] などの「演算子での操作」を、関数として呼び出せるようにしたもの。

Proxy とセットで使うことが多く、trap の中で Reflect.xxx を呼ぶことで、「横取りしたあとに本来の挙動を呼び出す」ことができる。

余裕が出てきたら触るポイント

少し慣れてきたら、次を試してみると理解が深まります。

Reflect.ownKeys を使って、Symbol を含めたすべてのキーを取得する。

Reflect.definePropertyObject.defineProperty の返り値の違い(true/false vs オブジェクト)を確認してみる。

Reflect.apply を Proxy の apply trap と組み合わせて、関数呼び出しにフックをかけつつ元の関数を呼ぶ。


まとめ

Reflect の本質は、
「オブジェクトや関数への基本的な操作を、統一された“関数 API”として提供する ES6 のメタプログラミング用の道具箱」
であることです。

押さえておきたいポイントは次の通りです。

従来の演算子や Object.xxx による操作を、Reflect.get / set / has / deleteProperty / defineProperty / ownKeys / apply などとして関数で呼び出せる。

戻り値が true/false でそろっているメソッドも多く、成功/失敗を扱いやすい。

Proxy の中で「標準の挙動に委譲したい」ときに、Reflect.xxx を使うのが定番パターン。

「新しいことをできるようにする」というより、「既存の操作を整理し、Proxy などと組み合わせやすくした」位置づけ。

最初の一歩としては、
小さなオブジェクトに対して

Reflect.get(obj, "foo");
Reflect.set(obj, "foo", 123);
Reflect.has(obj, "foo");
Reflect.deleteProperty(obj, "foo");
JavaScript

を実際に手で打ってみて、
「普段やっていることの“関数版”なんだな」という感覚を掴んでみてください。

そのうえで Proxy のコードを見たときに、
Reflect.get, Reflect.set を見つけて
「あ、ここで本来の挙動を呼んでいるんだな」と分かれば、
Reflect はもうあなたの中で「難しい謎のオブジェクト」ではなく、
「Proxy と並んで使う、わりと地味だけど頼れる相棒」に変わっているはずです。

タイトルとURLをコピーしました