30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

constructorプロパティについて

JavaScriptの関数は原則的に、生成された瞬間にprototypeプロパティを持つ。
その中身はオブジェクトであり、constructorプロパティのみを持つ。このプロパティは、関数自身を参照している。

function myFunc(){};

console.log(Object.getOwnPropertyNames(myFunc));
// [ 'length', 'name', 'arguments', 'caller', 'prototype' ]
// 明示しなくても、上記のプロパティを持つ

console.log(Object.getOwnPropertyNames(myFunc.prototype));
// [ 'constructor' ]
// prototypeが持っているプロパティはconstructorのみ

console.log(myFunc.prototype.constructor === myFunc);   // true
// prototypeのconstructorは、myFuncを参照している

このような仕組みであるため、インスタンスconstructorプロパティは通常、コンストラクタを返す。

function myFunc(){};

console.log(Object.getOwnPropertyNames(myFunc));
// [ 'length', 'name', 'arguments', 'caller', 'prototype' ]

console.log(Object.getOwnPropertyNames(myFunc.prototype));
// [ 'constructor' ]

console.log(myFunc.prototype.constructor === myFunc);   // true
// prototypeのconstructorは、myFuncを参照している

var myInstance = new myFunc();

console.log(myInstance.constructor === myFunc); // true

console.log(myInstance.hasOwnProperty('constructor')); // false
console.log(myInstance.__proto__.hasOwnProperty('constructor'));   // true
// myInstance自身はconstructorプロパティを持っていないため、
// プロトタイプのconstructorプロパティを参照する。

しかし、あくまでも参照先のプロトタイプオブジェクトの値を返しているだけなので、インスタンス自身にプロパティを定義したり、プロトタイプオブジェクト自体を書き換えた場合は、挙動が変わってくる。

function Person(){};

var person1 = new Person();

// インスタンスのconstructorは通常、コンストラクタを指すが……
console.log(person1.constructor === Person);    // true

person1.constructor = 'foo';
console.log(person1.constructor === Person);    // false
// インスタンス自身にconstructorプロパティを追加すると当然、
// そちらを参照するようになる。

Person.prototype = {
    someFunc: function(){}
};
var person2 = new Person();
console.log(person2.constructor === Person);    // false
console.log(person2.constructor === Object);   // true
// プロトタイプオブジェクトを上書きすると、
// constructorプロパティはObjcetを参照するようになる。

// 新しく定義したPerson.prototypeはconstructorを持っていないため、
// プロトタイプオブジェクトのconstructorを参照する。
// そこにはObjectが格納されているため、このような挙動になる。
console.log(Person.prototype.hasOwnProperty('constructor'));   // false
console.log(Person.prototype.__proto__.hasOwnProperty('constructor')); // true
console.log(Person.prototype.__proto__.constructor === Object);    // true

// プロトタイプオブジェクトを上書きする際に改めてconstructorプロパティを定義すれば、
// 上記のような挙動は防げる。
Person.prototype = {
    constructor: Person,
    someFunc: function(){}
};
var person3 = new Person();
console.log(person3.constructor === Person);    // true

また、.prototype.constructorは他のプロパティ同様、デフォルトでは書き換え可能になっているため、この値を変更してしまうことも可能。
さらに、操作可能でもあるため、削除してしまうことも可能になっている。

function Person(){};

var person1 = new Person();

console.log(person1.constructor === Person);    // true

console.log(Object.getOwnPropertyDescriptor(Person.prototype, 'constructor'));
// { value: [Function: Person],
//   writable: true,
//   enumerable: false,
//   configurable: true }
// 操作可能、かつ書き込み可能であることが分かる

// 書き換え
Person.prototype.constructor = 'hoge';
console.log(person1.constructor === Person);    // false
console.log(person1.constructor === 'hoge');   // true

// プロトタイプから削除
delete Person.prototype.constructor;
console.log(person1.constructor === 'hoge');   // false
console.log(person1.constructor === Object);   // true

console.log(Person.prototype.__proto__ === Object.prototype);  // true
console.log(Person.prototype.__proto__.constructor === Object);    // true

// プロトタイプのプロトタイプからも削除
delete Person.prototype.__proto__.constructor;
console.log(person1.constructor === Object);   // false
console.log(person1.constructor === undefined);   // true

上記の例では、プロトタイプチェーンの終端(Object.prototype)まで辿ってconstructorを削除したため、

person1.constructor === undefined

となる。