Object.prototype.constructor
constructor
プロパティは、インスタンスオブジェクトを生成した Object
のコンストラクター関数への参照を返します。なお、このプロパティの値は関数そのものへの参照であり、関数名を含んだ文字列ではありません。
値が 1
, true
, "test"
のようなプリミティブ値の場合は読み取り専用です。
解説
(Object.create(null)
で生成されたオブジェクトを除いて) すべてのオブジェクトが constructor
プロパティを持っています。明示的にコンストラクター関数を用いることなく生成されたオブジェクト (オブジェクトリテラルや配列リテラルなど) は、 constructor
プロパティがそのオブジェクトの基礎オブジェクトのコンストラクター型を指します。
js
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
プロパティを表示します。
js
function Tree(name) {
this.name = name;
}
let theTree = new Tree("Redwood");
console.log("theTree.constructor is " + theTree.constructor);
この例の出力は次のとおりです。
js
theTree.constructor is function Tree(name) {
this.name = name
}
オブジェクトのコンストラクターの変更
constructor
プロパティに代入することができるのは、対応するコンストラクター関数を持たない null
および undefined
以外の値 (String
、Number
、Boolean
など) ですが、プリミティブ値には変更が適用されません (例外は発生しません)。これは、プリミティブ値 (null
と undefined
を除く) にどのようなプロパティを設定しても効果がないのと同じ仕組みによるものです。つまり、このようなプリミティブをオブジェクトとして使用すると、対応するコンストラクターのインスタンスが生成され、文の実行後すぐに破棄されることになります。
js
let val = null;
val.constructor = 1; //TypeError: val is null
val = "abc";
val.constructor = Number; //val.constructor === String
val.foo = "bar"; //暗黙的に String('abc') のインスタンスが生成され、 foo プロパティに代入する
val.foo === undefined; //true になる。 String('abc') の新しいインスタンスがこの比較のために生成され、 foo プロパティがないため
つまり、上記のプリミティブ値を除いて、基本的に constructor
プロパティの値を変更することができます。なお、 constructor
プロパティを変更しても、 instanceof 演算子には影響しません。
js
let a = [];
a.constructor = String;
a.constructor === String; // true
a instanceof String; //false
a instanceof Array; //true
a = new Foo();
a.constructor = "bar";
a.constructor === "bar"; // true
//etc.
オブジェクトが封印または凍結されていた場合は、変更の効果がなくなり、例外は発生しません。
js
let a = Object.seal({});
a.constructor = Number;
a.constructor === Object; //true
関数のコンストラクターの変更
多くの場合、このプロパティは関数コンストラクターとしての関数の定義に使用され、将来の new およびプロトタイプ継承チェーンでの呼び出しに使われます。
js
function Parent() {
/* ... */
}
Parent.prototype.parentMethod = function parentMethod() {};
function Child() {
Parent.call(this); // すべてが正しく初期化されていることを確認
}
Child.prototype = Object.create(Parent.prototype); // 子のプロトタイプを親のプロトタイプで再定義
Child.prototype.constructor = Child; // 元のコンストラクターとして Child を返す
しかし、いつこの最後の行を実行する必要があるのでしょうか。残念ながら、正しい答えは、場合によるということです。
元のコンストラクターを再割り当てすることが重要である場合と、これがコードの未使用の一行になる場合を定義してみましょう。
以下の場合を見てみてください。オブジェクトが自分自身を生成するために create()
メソッドを持っています。
js
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 にリンクしているため、例外が発生します。
これを防ぐには、利用しようとしている必要なコンストラクターを代入するだけです。
js
function Parent() {
/* ... */
}
function CreatedConstructor() {
/* ... */
}
CreatedConstructor.prototype = Object.create(Parent.prototype);
CreatedConstructor.prototype.constructor = CreatedConstructor; // 将来使用するために正しいコンストラクターを設定
CreatedConstructor.prototype.create = function create() {
return new this.constructor();
};
new CreatedConstructor().create().create(); // とてもよくなった
これで、コンストラクターの変更が有用である理由が明確になりました。
もう一つの例を考えてみましょう。
js
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.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,
};
};
この例を正しく動作させるためには、コンストラクターとして Parent
を保持するか、静的プロパティを Child
のコンストラクターに再代入するかする必要があります。
js
...
Child = Object.assign(Child, ParentWithStatic); // Notice that we assign it before we create(...) a prototype below
Child.prototype = Object.create(ParentWithStatic.prototype);
...
または、 Parent
のコンストラクター識別子を Child
コンストラクター関数の別のプロパティに代入し、そのプロパティからアクセスします。
js
...
Child.parentConstructor = ParentWithStatic
Child.prototype = Object.create(ParentWithStatic.prototype)
...
let startPosition = this.constructor.parentConstructor.getStartPosition()
...
メモ: コンストラクターを手動で更新したり設定したりすると、異なる結果や混乱する結果を導くことがあります。これを防ぐためには、それぞれの場合に応じて constructor
の役割を定義することが必要です。多くの場合、 constructor
使用されず、再割り当ての必要はありません。
仕様書
Specification |
---|
ECMAScript Language Specification # sec-object.prototype.constructor |
ブラウザーの互換性
BCD tables only load in the browser
関連情報
- クラス宣言
- クラスのコンストラクター
- 用語集: コンストラクター