JS メタプログラミング
1. メタプログラミングとは
メタプログラミングとは、プログラムが自分自身を操作するための様々な手法を指します。具体的には以下のようなことが可能です。
- 実行時にオブジェクトを修正する
- 実行時にオブジェクトを検査(インスペクション)する
- 実行時にオブジェクトを制御する
- 実行中のオペレーションをインターセプト(遮断・割り込み)する
- 関数やクラスを修正する
- 動的にコードを生成する
1.1 わかりやすい解説
通常、コードはデータを扱います。
メタプログラミングでは、コードがコードを扱います。
2. オブジェクトのインスペクション
Object.keys() メソッドを使用すると、オブジェクトのプロパティを検査(インスペクション)できます。Object.keys() を使うことは、メタプログラミングの最もシンプルな例の一つです。
実装例:
このコードは、別のコード(オブジェクト)を分析しています。
// オブジェクトを作成
const user = {name: "Jan", age: 40};
// オブジェクトのキーを配列に格納
const myArr = Object.keys(user);3. オブジェクトの変更
典型的なメタプログラミングのタスクは、オブジェクトの振る舞いを修正することです。
実装例:
// オブジェクトを作成
const person = {name: "John", age: 41};
// "name" プロパティが常に "secret" を返すように定義
Object.defineProperty(person, "name", {
get() { return "secret"; }
});
let name = person.name; // "secret" が返される4. 動的コードの生成
メタプログラミングには、動的なコード生成も含まれます。
JavaScript は実行時に関数を生成することができます。
実装例:
const fn = new Function("a", "b", "return a + b");5. メタプログラミングの活用例
| コンセプト | 説明 |
|---|---|
| バリデーション | プロパティに設定できる値を制限する |
| ロギング | Proxy を使用してプロパティの変更を表示する |
| デバッグ | Proxy を使用してオペレーションをインターセプトする |
| フレームワーク | Vue、MobX、Svelte などが状態変化を検知するために使用 |
| ORM / データベースマッピング | オブジェクトをラップし、DBスキーマに基づいてフィールドを作成する |
| 動的 API | 実行時に関数やオブジェクト構造を作成する |
6. Proxy によるメタプログラミング
JavaScript における Proxy と Reflect という2つのオブジェクトは、メタレベルでのプログラミングを可能にします。
Proxy を使用すると、値の読み取りや書き込みといったプロパティ操作をインターセプトできます。
以下の例では:
userオブジェクトがProxyでラップされています。- Proxy は
set()トラップを使用して、プロパティが設定されるたびにログを出力します。
実装例(プロパティ値の変更をログ出力):
// オブジェクトを作成
const user = {name: "Jan", age: 40};
// オブジェクトを Proxy でラップ
const proxy = new Proxy(user, {
// set トラップを使用
set(target, property, value) {
// 変更をログ出力
log(property + "; " + value);
return target[property] = value;
}
});
// プロパティを変更
proxy.name = "John";
proxy.age = 45;
proxy.name = "Paul";7. Proxy と Reflect の併用
Reflect を使用することで、Proxy の動作を通常のオブジェクトの動作と一致させ、より安全に処理を委譲できます。
以下の例では:
userオブジェクトがProxyでラップされています。- Proxy は
set()トラップを使用して、プロパティ設定時にログを出力します。 setトラップ内でReflect.set()を使用し、本来の動作を安全に転送(フォワーディング)しています。
実装例:
// オブジェクトを作成
const user = {name: "Jan", age: 40};
// オブジェクトを Proxy でラップ
const proxy = new Proxy(user, {
// set トラップを使用
set(target, property, value) {
// 変更をログ出力
log(property + ": " + value);
// Reflect を使用して安全に本来の処理へ転送
return Reflect.set(target, property, value);
}
});