オブジェクトリフレクション

【JavaScript】 組み込みオブジェクトReflectって何?

更新日:2021/12/11

JavaScriptにはReflectというオブジェクトがあります。
このオブジェクトはECMAScriptで標準組み込みオブジェクトとして定義されているのに、非常にマイナーです。

そこで今回はReflectオブジェクトの使い方と存在意義についてお伝えします。

 

Reflectオブジェクトとは

プログラミング用語として『リフレクション』というものがあります。
JavaScriptのReflectオブジェクトは、これに関連するものであることが想像できます。

そこでまずは『リフレクション』についてざっくりと解説してみます。

リフレクションとは

リフレクション(reflection)の意味として反射を思い浮かべる方が多いと思います。
しかしプログラミングの場合は『反省』『自省』など自分自身について考察するといった意味が近いです。

プログラミングにおけるリフレクションは、オブジェクトやプロパティについての情報(アクセス制限や名前・型など)の取得および変更、取得した情報からの関数実行などを動的におこなう機能を指します。
ただし取得できる情報や変更できる範囲などは言語によって異なります。

なお、情報を取得したり調査する機能はイントロスペクションと呼ばれています。
次のようにtypeof演算子などを使用して型確認をおこなうことを型イントロスペクションと呼ぶことがあります。


if( typeof value === "number" ) { ・・・ }

イントロスペクションはリフレクションを構成する機能の一つです。
しかし言語によっては、次のようなパターンもあります。

  • 同じ機能という位置づけ
  • 異なる機能として区別
  • 逆の位置づけ(リフレクションは調査機能。イントロスペクションは変更/実行できる機能)

動的にオブジェクトにプロパティを追加する機能などもリフレクションです。
上記のtypeofの他にも、JavaScriptには構文としてリフレクション機能がサポートされています。

その上で、JavaScriptのReflectはリフレクションをメソッドとして提供しているオブジェクト、という位置づけです。

リフレクションを構成する機能の一つにインターセッションという機能があります。
これはオブジェクトなどの標準的な動作に介入して、独自の処理をおこなうものです。
JavaScriptはProxyオブジェクトでインターセッションを組み込むことができます。
Proxyについては、次のページを読んでみてください。
【JavaScript】 Proxyオブジェクトの使い方と仕組み

Reflectオブジェクトの概要

Reflectオブジェクトはコンストラクターではないので、new演算子を使用しません。
直接、メソッドを呼び出します。

Reflectオブジェクトは、特定の演算子(newなど)と同じ動作をするメソッドが定義されています。
また、他のオブジェクトで定義されているプロパティ操作に関連するメソッドが、Reflectオブジェクトにも定義されています。

例えば割り当て演算子(=)は、右側がオブジェクトのプロパティのとき、対応するプロパティから値が取得されます。
また左側がオブジェクトのプロパティのとき、対応するプロパティに値がセットされます。

割り当て演算子は値の取得とセットが行われる


const obj1 = { prop1 : 100 };
const obj2 = { prop2 : 200 };
obj1.prop1 = obj2.prop2;
console.log( obj1.prop1 ); // 200

上のコードの割り当て部分をReflectのsetとgetメソッドで書き換えることができます。

Reflectへの置き換え


Reflect.set( obj1,"prop1", Reflect.get( obj2 , "prop2" ) );
console.log( obj1.prop1 ); / 200

なお指定できるのはオブジェクトのプロパティなので、変数の操作はできません。
つまりobj1とobj2への割り当て部分を書き換えることはできません。

よくある勘違いが『リフレクションを実現したときはReflectオブジェクトを使用する』というものです。

JavaScriptはtypeofやinstanceof演算子、Objectオブジェクトのメソッドなどでリフレクションがサポートされています。
これらは既に使用しているという人も多いと思います。
つまり既にリフレクションを利用したプログラミングをしているのです。

そのため今まで通りのプログラミングでよく、わざわざReflectオブジェクトを使用する必要はありません。

Reflectオブジェクトの使いどころ:一部の動作が異なる

Reflectオブジェクトは、基本的には積極的に使用するものではありません。

しかし、演算子ではできない機能や、メソッドの返り値が変更されているものがあります。
それらの機能を利用する必要があるときに、Reflectオブジェクトを使用します。
(例を出そうと思いましたが、思いつきませんでした…)

■機能が拡張されているもの

Reflect.apply() : this値を変更できる
Reflect.construct() : プロトタイプを異なるオブジェクトに変更できる
Reflect.get() : this値を変更できる
Reflect.set() : this値を変更できる

■戻り値が異なるもの

Reflect.defineProperty() : 真偽値を返す
Reflect.deleteProperty() : 真偽値を返す
Reflect.ownKeys() : シンボルを含めたプロパティを返す

■その他

Reflect.isExtensible() : オブジェクト以外の時エラーをスロー
Reflect.preventExtensions() : オブジェクト以外の時エラーをスロー

 

Reflectオブジェクトのメソッド

Reflectオブジェクトは次の表のメソッドを持っています。

Reflectのメソッド対応する機能・メソッド
Reflect.apply()関数( )
Reflect.construct()new 関数( )
Reflect.defineProperty()Object.defineProperty( )
Reflect.deleteProperty()delete オブジェクト.プロパティ
Reflect.get()オブジェクト.プロパティ または オブジェクト[ プロパティ ]
Reflect.getOwnPropertyDescriptor()Object.getOwnPropertyDescriptor( )
Reflect.getPrototypeOf()Object.getPrototypeOf( )
Reflect.has()プロパティ in オブジェクト
Reflect.isExtensible()Object.isExtensible( )
Reflect.ownKeys()[...Object.getOwnPropertyNames( ) , ...Object.getOwnPropertySymbols( )]
Reflect.preventExtensions()Object.preventExtensions( )
Reflect.set()オブジェクト.プロパティ = 値 または オブジェクト[ プロパティ ] = 値
Reflect.setPrototypeOf()Object.setPrototypeOf( )

Reflect.apply()

Reflect.apply()は、関数またはメソッドを実行します。

構文

Reflect.apply( 関数・メソッド , this値 , 引数配列 )

※引数は3つとも省略できません。

Reflect.apply()は、Function.prototype.apply()と似た機能です。

Function.prototype.apply()については、こちらを読んでみてください。
【JavaScript】 そろそろcall()とapply()を理解してみようと思う

実行例:

関数の実行


const func = (a,b,c)=> a * b * c;

console.log( func( 1 , 2 , 3) );  // 6
console.log( Reflect.apply( func, null , [1 , 2 , 3] ) ); // 6

this値の変更


const obj = {
    a:1,b:2,c:3,
    func:function(){
        return this.a * this.b * this.c;
    }
};
const objData = { a: 10 , b:2 , c:3 };

console.log( obj.func.apply( objData ) ); // 60
console.log( Reflect.apply(obj.func, objData ,[]) ); // 60

Reflect.construct()

Reflect.construct()は、new演算子でコンストラクターを実行したときと同じ結果を返します。

構文

Reflect.construct( コンストラクター , 引数配列 , プロトタイプ引用コンストラクター )

※引数:プロトタイプ引用コンストラクターは省略可能

3番目の引数に指定したコンストラクターのprototypeプロパティが、生成されるインスタンスのプロトタイプチェーンに組み込まれます。
ただしProxyオブジェクトのときは、Proxyオブジェクトが対象としているオブジェクトのprototypeプロパティが使用されます。
3番目の引数を省略すると、一番目の引数が使用されます。

実行例:

インスタンスの作成


const constructorFunc = function(a,b,c){
    [this.a,this.b,this.c] = [a,b,c];
}
constructorFunc.prototype.func = function(){
    return this.a * this.b * this.c;
};
console.log( (new constructorFunc( 1, 2 , 3)).func() ); // 6
console.log( Reflect.construct( constructorFunc , [1, 2 , 3]).func() ); // 6

3番目の引数を指定


const constructorFunc = function(a,b,c){
    [this.a,this.b,this.c] = [a,b,c];
}
const obj = function(){};
obj.prototype.func = function(){return this.a * this.b * this.c;};

console.log( Reflect.construct( constructorFunc , [1, 2 , 3] , obj).func() );

Reflect.defineProperty()

Reflect.defineProperty()はオブジェクトにプロパティを追加または変更します。
Object.defineProperty()と同じ機能ですが、戻り値が異なります。

Object.defineProperty():指定したオブジェクトを返す
Reflect.defineProperty():結果を真偽値で返す

definePropertyについては、次のページを読んでみてください。
【JavaScript】 definePropertyメソッドとは?通常のプロパティ追加との違い

構文

Reflect.defineProperty( オブジェクト , プロパティ名 , プロパティ記述子 )

実行例:


const propDescriptor = {
    value:100,
};
const obj1 ={};
Object.defineProperty(obj1, "prop", propDescriptor);
const obj2 ={};
Reflect.defineProperty(obj2, "prop", propDescriptor);
console.log( obj1.prop , obj2.prop ); // 100 , 100

Object.definePropertyはプロパティの変更ができなかったときエラーをスローします。
そのためエラー判定は、try-catchでおこないます。
一方Reflect.definePropertyは真偽値を返すので、if で確認できます。

つぎのコードは同じプロパティを再定義しています。
しかしdefinePropertyはwritableおよびconfigurable属性の規定値をfalseとして扱うため、再定義するとエラーとなります。


const propDescriptor = {value:100,};
const propDescriptor2 = {value:200,};

try{
    const obj1 ={};
    Object.defineProperty(obj1, "prop", propDescriptor);
    Object.defineProperty(obj1, "prop", propDescriptor2);
}catch(e){
    console.log( e );
}

const obj2 ={};
Reflect.defineProperty(obj2, "prop", propDescriptor)
if( Reflect.defineProperty(obj2, "prop", propDescriptor2) === false ){
    console.log( "エラー");
}

ただしReflect.definePropertyは第一引数がオブジェクトでないときや、プロパティ記述子の内容が正しくないときはエラーをスローします。
そのため if文でのチェックができません。


const propDescriptor = { value:100,get:()=>{} }; 
Reflect.defineProperty(obj2, "prop", propDescriptor):
// TypeError: property descriptors must not specify a value or be writable when a getter or setter has been specified
//   和訳:プロパティ記述子は、ゲッターまたはセッターが指定されている場合、値を指定したり、書き込み可能であってはなりません

Reflect.deleteProperty()

Reflect.deleteProperty()は、deleteキーワードと同じ動作をおこないます。

Reflect.deleteProperty( オブジェクト , プロパティ名 )

実行例:


const obj1 = {prop:100};
const obj2 = {prop:100};
delete obj1.prop;
Reflect.deleteProperty( obj2 , "prop" );
console.log( obj1.prop , obj2.prop );

Reflect.get()

Reflect.get()はプロパティから値を取得します。
ゲッタープロパティは、登録されているゲッター関数が実行されます。

構文

Reflect.get( オブジェクト , プロパティ名 , thisオブジェクト )

thisオブジェクトは省略可能です。
ゲッター関数のthis値として使用されます。

実行例:


const obj1 = {
    prop1:100,
    func:function(){
        console.log( "func関数を実行された" );
    }
}
    // ゲッタープロパティの定義
Reflect.defineProperty( obj1 , "prop2" , {
    get:function (){
        return 500;
    }
});
console.log( Reflect.get( obj1 ,"prop1" ) ); // 100
console.log( Reflect.get( obj1 ,"prop2" ) ); // 500
   // 取得したメソッドは実行可能
Reflect.get( obj1 ,"func" )( );  // "func関数を実行された"

3番目の引数を指定した場合


const obj1 = {
    prop1:100,
}
Reflect.defineProperty( obj1 , "prop2" , {
    get:function (){
        return this.prop1;
    }
});
  // 3番目の値を指定しない場合
console.log( Reflect.get( obj1 ,"prop2" ) );  // 100
  // 3番目の値を指定した場合
const dataObj =  { prop1:300 };
console.log( Reflect.get( obj1 ,"prop2" , dataObj ) ); // 300

Reflect.getOwnPropertyDescriptor()

Reflect.getOwnPropertyDescriptor()は、プロパティのプロパティ記述子を取得します。
Object.getOwnPropertyDescriptor()と同じ機能ですが、次の点が異なります。

第一引数がオブジェクトではないとき
Reflect.getOwnPropertyDescriptor() : エラー
Object.getOwnPropertyDescriptor() : オブジェクトに変換

構文

Reflect.getOwnPropertyDescriptor( オブジェクト , プロパティ名 )

実行例:


const obj1 = {
    prop1:100
}
console.log( Object.getOwnPropertyDescriptor( obj1 ,"prop1" ) );
console.log( Reflect.getOwnPropertyDescriptor( obj1 ,"prop1") );
 // どちらも次を返す
 // Object { value: 100, writable: true, enumerable: true, configurable: true }

第一引数にプリミティブを指定した場合


console.log( Object.getOwnPropertyDescriptor( "text" , "length" ) );
  // Object { value: 4, writable: false, enumerable: false, configurable: false }
console.log( Reflect.getOwnPropertyDescriptor( "text" , "length" ) );
 //  TypeError: "text" is not a non-null object

※文字列プリミティブが暗黙的にオブジェクト変換されると、その結果としてStringオブジェクトが生成されます。
詳しくは次のページを読んでみてください
【JavaScript】 プリミティブの奇妙な振る舞いとラッパーオブジェクト

Reflect.getPrototypeOf()

Reflect.getPrototypeOf()は、オブジェクトのプロトタイプを取得します。
Object.getPrototypeOf()と同じ機能ですが、次の点が異なります。

第一引数がオブジェクトではないとき
Reflect.getPrototypeOf() : エラー
Object.getPrototypeOf() : オブジェクトに変換

構文

Reflect.getPrototypeOf( オブジェクト )

実行例:


const obj = function (){};
obj.prototype.func = function (){};
const objInstance = new obj();
console.log( Object.getPrototypeOf( objInstance  ) );
console.log( Reflect.getPrototypeOf( objInstance  ) );
 // どちらも次を返す
 // Object { func: func(), … }

第一引数にプリミティブを指定した場合


console.log( Object.getPrototypeOf( "text"  ) );
  // String { "" }
console.log( Reflect.getPrototypeOf( "text" ) );
 //  TypeError: `target` argument of Reflect.getPrototypeOf must be an object, got the string "text"

※文字列プリミティブが暗黙的にオブジェクト変換されると、その結果としてStringオブジェクトが生成されます。
詳しくは次のページを読んでみてください
【JavaScript】 プリミティブの奇妙な振る舞いとラッパーオブジェクト

Reflect.has()

Reflect.has() は、プロトタイプチェーンを含めて、オブジェクトが指定したプロパティを持っているか確認します。
in 演算子と同じ結果となります。

構文

Reflect.has( オブジェクト , プロパティ名 )

実行例:


const obj = function (){ this.prop = 100; };
obj.prototype.func = function (){};
const objInstance = new obj();
console.log( "prop" in objInstance ); // true
console.log( Reflect.has( objInstance , "prop" ) ); // true
console.log( "func" in objInstance ); // true
console.log( Reflect.has( objInstance , "func" ) ); // true

Reflect.isExtensible()

Reflect.isExtensible()は、オブジェクトを拡張可能かどうか、つまりプロパティを追加できるかどうかを確認できます。
Object.isExtensible()と同じ機能ですが、次の点が異なります。

第一引数がオブジェクトではないとき
Reflect.isExtensible() : エラー
Object.isExtensible() : オブジェクトに変換

構文

Reflect.isExtensible( オブジェクト )

実行例:


const obj = {  };
console.log( Object.isExtensible( obj ) ); // true
console.log( Reflect.isExtensible( obj ) ); // true
Object.freeze( obj )
console.log( Object.isExtensible( obj ) ); // false
console.log( Reflect.isExtensible( obj ) ); // false

オブジェクトのプロパティ追加を禁止は、freeze等のメソッドを使用します。
【JavaScript】 オブジェクトのプロパティ追加を禁止する方法

Reflect.ownKeys()

Reflect.ownKeys()はオブジェクトが所持するプロパティの名前を配列で取得します。
ただしプロパティーチェーン上のプロパティは取得しません。

このメソッドに相当する機能は存在しませんが、Object.getOwnPropertyNames() と Object.getOwnPropertySymbols() の両方の結果を結合したものと同じです。

構文

Reflect.ownKeys( オブジェクト )

実行結果:


const symbol = Symbol();
const obj = function (){
    this.prop = 100;
    this[symbol] = 200;
};
obj.prototype.protoProp = 300;
const objInstance = new obj();

console.log( Object.getOwnPropertyNames( objInstance ) , Object.getOwnPropertySymbols( objInstance ) );
    // Array [ "prop" ] , Array [ Symbol() ]
console.log( Reflect.ownKeys( objInstance ) );
   // Array [ "prop", Symbol() ]

Reflect.preventExtensions()

Reflect.preventExtensions()は、オブジェクトの拡張可能、つまりプロパティを追加を禁止します。
Object.preventExtensions()と同じ機能ですが、次の点が異なります。

第一引数がオブジェクトではないとき
Reflect.isExtensible() : エラー
Object.isExtensible() : オブジェクトに変換

構文

Reflect.preventExtensions( オブジェクト )

実行結果:


const obj = {}
obj.prop = 100;
Object.preventExtensions( obj );
obj.prop2 = 200;
console.log( obj ); // Object { prop: 100 }

const obj2 = {}
obj2.prop = 100;
Reflect.preventExtensions( obj2 );
obj2.prop2 = 200;
console.log( obj2 ); // Object { prop: 100 }

オブジェクトのプロパティ追加が禁止されていてもエラーが発生しません。
ただし、strictモードのときはエラーが発生します。

Reflect.set()

Reflect.set()はプロパティに値をセットし、結果を真偽値で返します。
セッタープロパティは、登録されているセッター関数が実行されます。

プロパティが存在しない場合は、作成されます。

構文

Reflect.set( オブジェクト , プロパティ名 , 値 , thisオブジェクト )

thisオブジェクトは省略可能です。
セッター関数のthis値として使用されます。

プロパティ名を省略するとundefinedとして扱われ、undefinedという名前のプロパティがないときは作成されます。

実行例:


const obj = {}
Reflect.set( obj , "prop" , 100 );
Reflect.set( obj );
console.log( obj ); // Object { prop: 100, undefined: undefined }

4番目の引数を指定した場合


const obj1 = {
    prop1:100,
}
Reflect.defineProperty( obj1 , "prop2" , {
    set:function ( value ){
        this.prop1 = value;
    }
});
// 4番目の値を指定しない場合
Reflect.set( obj1 , "prop2" , 500 );
console.log( obj1 ); // Object { prop1: 500, prop2: Setter }
// 4番目の値を指定した場合
const obj2 = {};
Reflect.set( obj1 , "prop2" , 1000 , obj2 );
console.log( obj1 , obj2 ); // Object { prop1: 500, prop2: Setter } Object { prop1: 1000 } 

注:

Reflect.set()は真偽値を返します。
そのため、複数の変数に同じ値をセットするなどセット後の変数の値が必要な場合、Reflect.set()と置き換えできません。


const obj={
    a:0,b:1,c:2
};
obj.a = obj.b = obj.c = 10;
console.log( obj ); // Object { a: 10, b: 10, c: 10 }

obj.a = obj.b = Reflect.set( obj , "c" , 10 );
console.log( obj ); // Object { a: true, b: true, c: 10 }

Reflect.setPrototypeOf()

Reflect.setPrototypeOf()は、オブジェクトにプロトタイプの参照先をセットします。
Object.setPrototypeOf()と同じ機能ですが、戻り値が異なります。

Reflect.setPrototypeOf() : 処理結果を真偽値で返す
Object.setPrototypeOf() : 対象のオブジェクトを返す

実行結果:


const prototype = {
    func:function(){
        console.log( this.prop1 );
    }
}
const obj1 = {
    prop1:100,
}
console.log( Object.setPrototypeOf( obj1, prototype ) ); // Object { prop1: 100 }
obj1.func(); // 100

const obj2 = {
    prop1:500,
}
console.log( Reflect.setPrototypeOf( obj2, prototype ) ); // true
obj2.func(); // 500

 

Reflectオブジェクトは基本的にいらない子

プログラミングを学び始めた頃の僕がリフレクションの意味を知ってJavaScriptにReflectオブジェクトの存在に気が付いたら、「リフレクション使っている俺ってかっこいい」みたいな感じで、ソースコードをReflect.get()とReflect.set()で埋め尽くしていたと思います。

個人的なものなら好きにやればいいけど、仕事でやったらめちゃくちゃ怒られます。
変数名だけで済むところをメソッド呼び出しをするなんてムダですし、コードの可読性が著しく低下します。
コードを入力するのも大変ですし、それを評価してくれる人はいません。
まさに『ムダな努力』ですね。

JavaScriptは演算子レベルである程度のリフレクションを実現しています。
typeofやinstanceof、配列形式でオブジェクトのプロパティにアクセスできる機能などです。
それらを使用した方が簡潔なコードを作成できますし、処理速度も速いです。

必要かどうかよく考えてから使用した方がいいですね。

更新日:2021/12/11

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。