web-dev-qa-db-ja.com

JavaScriptの継承とコンストラクターのプロパティ

次のコードについて考えてみます。

_function a() {}
function b() {}
function c() {}

b.prototype = new a();
c.prototype = new b();

console.log((new a()).constructor); //a()
console.log((new b()).constructor); //a()
console.log((new c()).constructor); //a()
_
  • Bとcのコンストラクターが更新されないのはなぜですか?
  • 私は継承を間違っていますか?
  • コンストラクターを更新する最良の方法は何ですか?

また、以下の点を考慮してください。

_console.log(new a() instanceof a); //true
console.log(new b() instanceof b); //true
console.log(new c() instanceof c); //true
_
  • _(new c()).constructor_がa()と等しく、Object.getPrototypeOf(new c())が_a{ }_であるとすると、instanceofnew c()cのインスタンスですか?

http://jsfiddle.net/ezZr5/

36

さて、ちょっとマインドゲームをしましょう:

上の画像から、次のことがわかります。

  1. function Foo() {}のような関数を作成すると、JavaScriptはFunctionインスタンスを作成します。
  2. すべてのFunctionインスタンス(コンストラクター関数)には、ポインターであるプロパティprototypeがあります。
  3. コンストラクター関数のprototypeプロパティは、そのプロトタイプオブジェクトを指します。
  4. プロトタイプオブジェクトには、ポインタでもあるプロパティconstructorがあります。
  5. プロトタイプオブジェクトのconstructorプロパティは、そのコンストラクター関数を指します。
  6. new Foo()のようにFooの新しいインスタンスを作成すると、JavaScriptは新しいオブジェクトを作成します。
  7. インスタンスの内部[[proto]]プロパティは、コンストラクターのプロトタイプを指します。

ここで、JavaScriptがプロトタイプではなくインスタンスオブジェクトにconstructorプロパティをアタッチしないのはなぜかという疑問が生じます。考えてみましょう:

function defclass(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

var Square = defclass({
    constructor: function (side) {
        this.side = side;
    },
    area: function () {
        return this.side * this.side;
    }
});

var square = new Square(10);

alert(square.area()); // 100

ご覧のとおり、constructorプロパティは、上記の例のareaのように、プロトタイプの単なる別のメソッドです。 constructorプロパティを特別なものにしているのは、プロトタイプのインスタンスを初期化するために使用されることです。それ以外は、プロトタイプの他の方法とまったく同じです。

プロトタイプでconstructorプロパティを定義すると、次の理由で有利になります。

  1. 論理的に正しいです。たとえば、Object.prototypeについて考えてみます。 Object.prototypeconstructorプロパティはObjectを指します。 constructorプロパティがインスタンスで定義されている場合、Object.prototype.constructorundefinedのインスタンスであるため、Object.prototypenullになります。
  2. 他のプロトタイプメソッドと同じように扱われます。これにより、すべてのインスタンスでnewプロパティを定義する必要がなくなるため、constructorの作業が簡単になります。
  3. すべてのインスタンスは同じconstructorプロパティを共有します。したがって、それは効率的です。

継承について話すとき、次のシナリオがあります。

上の画像から、次のことがわかります。

  1. 派生コンストラクターのprototypeプロパティは、基本コンストラクターのインスタンスに設定されます。
  2. したがって、派生コンストラクターのインスタンスの内部[[proto]]プロパティもそれを指します。
  3. したがって、派生コンストラクターインスタンスのconstructorプロパティは、ベースコンストラクターを指すようになりました。

instanceof演算子に関しては、一般的な考えに反して、インスタンスのconstructorプロパティに依存しません。上からわかるように、それは誤った結果につながるでしょう。

instanceof演算子は二項演算子です(2つのオペランドがあります)。インスタンスオブジェクトとコンストラクター関数を操作します。 Mozilla Developer Network で説明されているように、それは単に次のことを行います。

function instanceOf(object, constructor) {
    while (object != null) {
        if (object == constructor.prototype) { //object is instanceof constructor
            return true;
        } else if (typeof object == 'xml') { //workaround for XML objects
            return constructor.prototype == XML.prototype;
        }
        object = object.__proto__; //traverse the prototype chain
    }
    return false; //object is not instanceof constructor
}

簡単に言うと、FooBarから継承する場合、Fooのインスタンスのプロトタイプチェーンは次のようになります。

  1. foo.__proto__ === Foo.prototype
  2. foo.__proto__.__proto__ === Bar.prototype
  3. foo.__proto__.__proto__.__proto__ === Object.prototype
  4. foo.__proto__.__proto__.__proto__.__proto__ === null

ご覧のとおり、すべてのオブジェクトはObjectコンストラクターから継承しています。プロトタイプチェーンは、内部の[[proto]]プロパティがnullを指すと終了します。

instanceof関数は、インスタンスオブジェクト(最初のオペランド)のプロトタイプチェーンをトラバースし、各オブジェクトの内部[[proto]]プロパティをコンストラクター関数のprototypeプロパティ(第2オペランド)。それらが一致する場合、trueを返します。それ以外の場合、プロトタイプチェーンが終了すると、falseが返されます。

64
Aadit M Shah

デフォルトでは、

function b() {}

その場合、b.prototypeには.constructorプロパティがあり、これは自動的にbに設定されます。ただし、現在、プロトタイプを上書きしているため、その変数を破棄しています。

b.prototype = new a;

その場合、b.prototypeには.constructorプロパティがなくなります。上書きで消去されました。 doesaから継承し、(new a).constructor === a、したがって(new b).constructor === a(プロトタイプチェーン内の同じプロパティを参照しています)。

後で手動で設定するのが最善の方法です。

b.prototype.constructor = b;

このための小さな関数を作成することもできます。

function inherit(what, from) {
    what.prototype = new from;
    what.prototype.constructor = what;
}

http://jsfiddle.net/79xTg/5/

12
pimvdb

constructorは、関数オブジェクトのprototypeプロパティのデフォルト値の通常の列挙不可能なプロパティです。したがって、prototypeに割り当てると、プロパティが失われます。

instanceofは、constructorを使用しないため、引き続き機能しますが、オブジェクトのプロトタイプチェーンをスキャンして、関数のprototypeプロパティの(現在の)値を探します。つまり、_foo instanceof Foo_はと同等です。

_var proto = Object.getPrototypeOf(foo);
for(; proto !== null; proto = Object.getPrototypeOf(proto)) {
    if(proto === Foo.prototype)
        return true;
}
return false;
_

ECMAScript3では、ユーザー定義のプロパティは常に列挙可能であるため(つまり、_for..in_に表示されるため)、組み込みのプロパティと同じように動作するconstructorプロパティを設定する方法はありません。

これはECMAScript5で変更されました。ただし、constructorを手動で設定した場合でも、コードにはまだ問題があります。特に、prototypeを親 'クラス'のインスタンスに設定することはお勧めできません。子クラスの場合は親コンストラクターを呼び出さないでください。 'が定義されますが、子インスタンスが作成されるときです。

これは、それがどのように行われるべきかについてのいくつかのECMAScript5サンプルコードです:

_function Pet(name) {
    this.name = name;
}

Pet.prototype.feed = function(food) {
    return this.name + ' ate ' + food + '.';
};

function Cat(name) {
    Pet.call(this, name);
}

Cat.prototype = Object.create(Pet.prototype, {
    constructor : {
        value : Cat,
        writable : true,
        enumerable : false,
        configurable : true
    }
});

Cat.prototype.caress = function() {
    return this.name + ' purrs.';
};
_

ECMAScript3で立ち往生している場合は、clone()の代わりにカスタム Object.create() function を使用する必要があり、constructornonを作成できません。 -列挙可能:

_Cat.prototype = clone(Pet.prototype);
Cat.prototype.constructor = Cat;
_
5
Christoph