Object.prototype.constructor
constructor プロパティは、インスタンスオブジェクトを生成した Object のコンストラクター関数への参照を返します。なお、このプロパティの値は関数そのものへの参照であり、関数名を含んだ文字列ではありません。
値が 1, true, "test" のようなプリミティブ値の場合は読み取り専用です。
解説
(Object.create(null) で生成されたオブジェクトを除いて) すべてのオブジェクトが constructor プロパティを持ちます。明示的にコンストラクター関数を用いることなく生成されたオブジェクト (オブジェクトリテラルや配列リテラルなど) は、 constructor プロパティがそのオブジェクトの基礎オブジェクトのコンストラクター型を指します。
let o = {}
o.constructor === Object // true
let o = new Object
o.constructor === Object // true
let a = []
a.constructor === Array // true
let a = new Array
a.constructor === Array // true
let n = new Number(3)
n.constructor === Number // true
例
オブジェクトのコンストラクターの表示
以下の例では、コンストラクターである Tree と、その方のオブジェクトである theTree を生成します。そして、 theTree オブジェクトの constructor プロパティを表示します。
function Tree(name) {
this.name = name
}
let theTree = new Tree('Redwood')
console.log('theTree.constructor is ' + theTree.constructor)
この例の出力は次のとおりです。
theTree.constructor is function Tree(name) {
this.name = name
}
オブジェクトのコンストラクターの変更
次の例は、一般的なオブジェクトのコンストラクターの値を変更する方法を示しています。 true, 1, "test" については、 (コンストラクターが読み取り専用のネイティブのものであるため) 影響を受けません。
この例は、オブジェクトの constructor プロパティに頼ることが常に安全とは限らないことを示しています。
function Type () {}
let types = [
new Array(),
[],
new Boolean(),
true, // 変わらない
new Date(),
new Error(),
new Function(),
function () {},
Math,
new Number(),
1, // 変わらない
new Object(),
{},
new RegExp(),
/(?:)/,
new String(),
'test' // 変わらない
];
for (let i = 0; i < types.length; i++) {
types[i].constructor = Type
types[i] = [types[i].constructor, types[i] instanceof Type, types[i].toString()]
}
console.log(types.join('\n'))
この例の出力は次の通りです (参考にコメントを追加しています)。
function Type() {},false, // new Array()
function Type() {},false, // []
function Type() {},false,false // new Boolean()
function Boolean() {
[native code]
},false,true // true
function Type() {},false,Mon Sep 01 2014 16:03:49 GMT+0600 // new Date()
function Type() {},false,Error // new Error()
function Type() {},false,function anonymous() {
} // new Function()
function Type() {},false,function () {} // function () {}
function Type() {},false,[object Math] // Math
function Type() {},false,0 // new Number()
function Number() {
[native code]
},false,1 // 1
function Type() {},false,[object Object] // new Object()
function Type() {},false,[object Object] // {}
function Type() {},false,/(?:)/ // new Regexp()
function Type() {},false,/(?:)/ // /(?:)/
function Type() {},false, // new String()
function String() {
[native code]
},false,test // 'test'
関数のコンストラクターの変更
多くの場合、このプロパティは new およびプロトタイプ継承チェーンで将来の呼び出しに使われる関数コンストラクターとしての関数の定義に使用されます。
function Parent() { /* ... */ }
Parent.prototype.parentMethod = function parentMethod() {}
function Child() {
Parent.call(this) // Make sure everything is initialized properly
}
Child.prototype = Object.create(Parent.prototype) // Child のプロトタイプを Parent のプロトタイプで再定義
Child.prototype.constructor = Child // Child の元のコンストラクターを復帰
しかし、いつこの最後の行を実行する必要があるのでしょうか。残念ながら、正しい答えは、場合によるということです。
元のコンストラクターを再割り当てすることが重要である場合と、これがコードの未使用の一行になる場合を定義してみましょう。
以下の場合を見てみてください。オブジェクトが自分自身を生成するために create() メソッドを持っています。
function Parent() { /* ... */ }
function CreatedConstructor() {
Parent.call(this)
}
CreatedConstructor.prototype = Object.create(Parent.prototype)
CreatedConstructor.prototype.create = function create() {
return new this.constructor()
}
new CreatedConstructor().create().create() // TypeError undefined is not a function since constructor === Parent
上記の例では、コンストラクターが Parent にリンクしているため、例外が発生します。
これを防ぐには、利用したいことに必要なコンストラクターを割り当てるだけです。
function Parent() { /* ... */ }
function CreatedConstructor() { /* ... */ }
CreatedConstructor.prototype = Object.create(Parent.prototype)
CreatedConstructor.prototype.constructor = CreatedConstructor // sets the correct constructor for future use
CreatedConstructor.prototype.create = function create() {
return new this.constructor()
}
new CreatedConstructor().create().create() // it's pretty fine
これで、コンストラクターの変更が有用である理由が明確になりました。
もう一つの例を考えてみましょう。
function ParentWithStatic() {}
ParentWithStatic.startPosition = { x: 0, y:0 } // Static member property
ParentWithStatic.getStartPosition = function getStartPosition() {
return this.startPosition
}
function Child(x, y) {
this.position = {
x: x,
y: y
}
}
Child = Object.assign(ParentWithStatic)
Child.prototype = Object.create(ParentWithStatic.prototype)
Child.prototype.constructor = Child
Child.prototype.getOffsetByInitialPosition = function getOffsetByInitialPosition() {
let position = this.position
let startPosition = this.constructor.getStartPosition() // error undefined is not a function, since the constructor is Child
return {
offsetX: startPosition.x - position.x,
offsetY: startPosition.y - position.y
}
};
この例では、正常に動作するように親のコンストラクターを維持するか、静的プロパティを子のコンストラクタに再割り当てする必要があります。
...
Child = Object.assign(ParentWithStatic) // Notice that we assign it before we create(...) a prototype below
Child.prototype = Object.create(ParentWithStatic.prototype)
...
または、親コンストラクターの識別子を子コンストラクター関数の別のプロパティに代入し、そのプロパティを介してアクセスします。
...
Child.parentConstructor = ParentWithStatic
Child.prototype = Object.create(ParentWithStatic.prototype)
...
let startPosition = this.constructor.parentConstructor.getStartPosition()
...
まとめ: コンストラクターを手動で更新したり設定したりすると、異なる結果や混乱する結果を導くことがあります。これを防ぐためには、それぞれの場合に応じてコンストラクターの役割を定義することが必要です。多くの場合、コンストラクター使用されず、再割り当ての必要はありません。
仕様書
ブラウザーの互換性
BCD tables only load in the browser
関連情報
- クラス定義
- クラスのコンストラクター
- 用語集: コンストラクター