関数・メソッド

【JavaScript】 definePropertyメソッドとは?通常のプロパティ追加との違い

更新日:2021/01/25

 

Object.defineProperty()とは

Object.defineProperty()を使用すると、オブジェクトにプロパティを作成できます。

こんな説明をされると、疑問に感じることがありますよね。

JavaScriptは、わざわざメソッドを使用しなくても、次のコードのように簡単にプロパティを作成できる機能が用意されています。


const obj = {}; // 空のオブジェクトobjを定義
obj.prop = 100;  // objに、propという名前のプロパティを作成し、100をセット

ではどのような場面で、definePropertyは使用されるのでしょうか。

definePropertyを使用する場面例

変数を定義するとき、letまたはconstというキーワードを使用します。

varというキーワードも存在しますが、あまり推奨されていないので、ここでは除外します。

letを使用すると変数の内容を変更できますが、constは変更しようとするとエラーが出て実行が停止します。


let val1 = 1;
val1 = 100;

const val2 = 1;
val2 = 100; // TypeError: invalid assignment to const 'val2'

一方、プロパティを作成するときは、letやconstなどの指定はありません。
しかし実質的にはletと同じ状態で定義されます。


const obj = {}; // 空のオブジェクトobjを定義
obj.prop = 100;  // objに、propという名前のプロパティを作成し、100をセット

obj.prop = 10000;  // 値を変更可能

では変数のconstのように、プロパティの値を変更させたくないときはどうすればいいのでしょうか?

Object.defineProperty()を使用すると、希望した設定で定義することができます。

この例の他にも、Object.defineProperty()は特殊な設定をおこなうことができます。

つまりObject.defineProperty()は、通常とは異なる設定でプロパティを作成したいときに使用します。

 

Object.defineProperty()の使用方法

Object.defineProperty()は、次のように定義されます。

定義

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

■引数

オブジェクト: プロパティを追加または変更したいオブジェクト

プロパティ名: 追加または変更したいプロパティの名前

プロパティ記述子: 属性名をキーとしたオブジェクト

プロパティ記述子の内容に問題がある場合、エラーがスローされます。


try{
    Object.defineProperty(obj, "prop2", {
        value:100,
        get:function (){}
    });
}catch (e) {
    console.log( e.message );
     // property descriptors must not specify a value or be writable when a getter or setter has been specified
}

■戻り値

オブジェクトで指定した値が返ります。

 

Object.defineProperty()の設定項目

プロパティ記述子設定項目一覧

種別

属性名

初期値

説明

データ

プロパティ

value

リテラル

変数・
プロパティ

undefinedプロパティにセットする値。

writable

true または falsefalsevalue属性で

セットした値を
後から上書き
できるかどうか
true:できる false:できない。

アクセサ

プロパティ

get

関数undefinedゲッター関数をセットする。

set

関数undefinedセッター関数をセットする。

共通

enumerable

true または falsefalsefor-in文や

Object.keys()
列挙されるかどうか
true:される false:されない

configurable

true または falsefalse

属性の変更ができるかどうか。
true:できる false:できない

Object.defineProperty()を使用しないときの属性

Object.defineProperty()を使用せず、通常の方法でプロパティを定義した場合、属性は次の値がセットされます。

value:指定した値
writable:true
enumerable:true
configurable:true

属性の確認はObject.getOwnPropertyDescriptor()を使用します。


const obj = {};
obj.prop = 100;

const od = Object.getOwnPropertyDescriptor( obj , "prop" ); // 引数: オブジェクト,プロパティ名
Object.keys(od).forEach(e=>console.log( `${e}:${od[e]}`) );
  // value:100
  // writable:true
  // enumerable:true
  // configurable:true

データプロパティ

データプロパティとは、プリミティブまたはオブジェクトを値に持つプロパティです。

Object.defineProperty()valueまたはwritable属性を指定すると、データプロパティが作成されます。

Object.defineProperty()valueまたはwritable属性を指定した場合、getまたはset属性を指定するとエラーになります。

なおデータプロパティの規定値は、writableenumerableconfigurable属性全てがfalseです。

通常の方法で追加したプロパティと異なるので、注意が必要です。

アクセサプロパティ

アクセサプロパティとは、ゲッター関数およびセッター関数を経由して値の取得やセットをおこなうプロパティです。

Object.defineProperty()getまたはset属性を指定すると、アクセサプロパティが作成されます。

Object.defineProperty()getまたはset属性を指定した場合、valueまたはwritable属性を指定するとエラーになります。

writable属性について

strictモードの挙動

writable属性にfalseを指定すると、値を上書き(代入)することができません。


const obj = {};

Object.defineProperty(obj, "prop2", {
    value: 100,
    writable:false, // 規定値
});
obj.prop2 = 1000; // TypeError: "prop2" is read-only

strictモードの場合、値を上書きするとエラーがスローされます。
しかし非strictモードの場合はエラーにならず、そのままコードが続行されます。

このような場合、プログラムコードの作成者がプロパティを上書き不可に設定してあることを忘れているケースが多いです。
そのため特定が困難なバグの原因になるので、Object.defineProperty()を使用するときはstrictモードの利用をおススメします。

writable属性の制御対象

変数やプロパティは、データの実体ではなくデータに割り振られたラベルのようなものを値として所持しています。

writable属性は、そのラベルを他のものに変更できるかどうかを表すものです。
実体そのものを上書きしているのではないのです。

プロパティ 上書き不可

オブジェクト値とwritable属性

value属性にオブジェクトを指定した場合、writable属性にfalseを指定して上書き不可にしても、そのオブジェクトにプロパティを追加することができます。


const obj = {};

Object.defineProperty(obj, "prop2", {
    value: {},       // 値にオブジェクトを指定
    writable:false, // 規定値
});
obj.prop2.data = 1000; // オブジェクトにプロパティを追加できる

上書き不可のはずなのに、プロパティを追加できるのは不思議に感じます。

しかしwritable属性の制御対象で説明したように、プロパティの上書き属性はオブジェクトの実体に影響を与えていません。
そのため、オブジェクトにプロパティを追加できます。

configurable属性がfalseのときの再定義

configurable属性をfalseObject.defineProperty()を実行し、同じプロパティに対して再度Object.defineProperty()を実行したとき、次の条件が適用されます。

  • configurable属性をtrueに変更できません。
  • writable属性がtrueなら、valueの変更が可能です。
  • writable属性をtrueからfalseに変更可能ですが、falseからtrueに変更できません。
  • writablegetset属性を変更できません。

プロトタイプチェーン/継承プロパティの影響

プロパティに代入を行う際、プロパティのwritable属性がチェックされて、falseなら代入できません。

これはプロトタイプチェーン上のプロパティと同名のプロパティを追加したい場合に大きな影響を与えています。

例えばあるオブジェクトにpropという名前のプロパティを追加したいとします。
ただし、このオブジェクトはプロトタイプチェーン上にpropというプロパティを持っています。

obj:{
  ○○○ ←ここに後からpropを追加したい
  [[Prototype]]:{←プロトタイプチェーン
       prop:20 // writableがfalse 
   }
}
obj.prop = 100;

上記コードの最後 obj.prop = 100; は、まずobjからpropが検索されます。
その結果プロトタイプチェーン上のpropが検出されます。
このとき、このプロパティのwritableがfalseなので、代入が失敗するのです。

通常の方法でプロパティを追加すると、writable属性はtrueで定義されます。そのため、同名のプロパティを追加できます。

通常は追加できる


const obj = function (){ // コンストラクター関数を定義
    this.prop1 = 10;
};
obj.prototype.prop2 = 20; // 通常の追加:writable属性はtrue

const obj2 = new obj();
  // obj2:{ prop1:10  ,
  //         [[Prototype]]:{prop2:20}←プロトタイプチェーン
  //      }
 
obj2.prop2 = 1000; // prop2を追加
  // obj2:{ prop1:10 , prop2 : 1000 , ← prop2が追加された
  //         [[Prototype]]:{prop2:20}←プロトタイプチェーン
  //      }

Object.defineProperty()でプロパティを追加すると、writable属性はデフォルトでfalseです。

writable属性をfalseに設定すると追加できない


const obj = function (){
    this.prop1 = 10;
};

Object.defineProperty(obj.prototype, "prop2", {
    value:100,
    writable:false, // 規定値
});

const obj2 = new obj();

obj2.prop2 = 1000; // prop2を追加 
  // obj2:{ prop1:10  , ← prop2が追加されていない!
  //         [[Prototype]]:{prop2:20}←プロトタイプチェーン
  //      }

もう一度書きますがwritable属性はデフォルトでfalseです。
Object.defineProperty()を使用する場合、意識的にtruefalseを判断する必要があります。

なお非strictモードの場合、上のコードでprop2を追加する操作が失敗してもエラーにならないので、注意が必要です。

 

Object.defineProperty()の使用例

プロパティを定数として扱いたい


const obj = {};

Object.defineProperty(obj, "prop2", {
    value:100, // 定数値
    writable:false, // 規定値
    configurable:false, // 規定値
    enumerable:true,
});

プロパティをセッターゲッターとして扱いたい


const obj = {
    data:100,
};

Object.defineProperty(obj, "prop2", {
    get:function (){
        return this.data;
    },
    set:function (data){
        this.data = data;
    },
    configurable:false, // 規定値
    enumerable:true,
});

プロパティを列挙させたくない


const obj = {
};

Object.defineProperty(obj, "prop2", {
    value:100, // 定数値
    writable:true, 
    configurable:false, // 規定値
    enumerable:true,
});

 

Object.defineProperties()で複数プロパティを一括指定

Object.defineProperty()はプロパティを一つ一つ定義していきますが、複数形のObject.defineProperties()を使用すると、複数のプロパティを一括で指定できます。

定義

Object.defineProperties( オブジェクト , プロパティ定義 )

■引数

オブジェクト: プロパティを追加または変更したいオブジェクト

プロパティ定義: プロパティ名をキー、プロパティ記述子を値として持つオブジェクト

■戻り値

オブジェクトで指定した値が返ります。

使用例


const obj = {
    data:100,
};

Object.defineProperties(obj, {
    prop2:{
        value:100,
        writable:true,
    },
    prop3:{
        get:function (){ return this.data; },
        set:function (data){ this.data = data; },
    },
});

 

既存プロパティのwritable、configurable属性を一括でfalseにする

Object.freeze()またはObject.seal()を使用すると、オブジェクトの既存プロパティを一括で上書き不可にしたり、オブジェクトのプロパティ追加を禁止したりできます。

メソッドプロパティ追加writableconfigurable
Object.freeze()禁止falsefalse
Object.seal()禁止変更なしfalse

詳しくは次のページを見てください。
【JavaScript】 オブジェクトのプロパティ追加を禁止する方法

更新日:2021/01/25

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

スポンサーリンク

記事の内容について

null

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

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

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

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

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

 

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