3

JavaScript にはクラス自体の概念がなく、すべてのオブジェクトの「型」は「オブジェクト」であることを認識して、「プロトタイプ」が何で構成されているか、特に、その「名前」がどのように関連付けられているか。たとえば、次のようになります。

function Foo(){};
console.log(Foo.prototype);                // => "Foo {}"

console.log中括弧の前に出力することをどのように知りFoo、その名前は何を指していますか?

(注:上記では、プロトタイプ自体ではなく関数のプロトタイププロパティを参照していることを認識しています(つまり、によってアクセスできるものではありません__proto__)が、同じ質問が実際のプロトタイプオブジェクトに適用されます。私の例を単純化するために、prototype プロパティを使用しました。)

更新: コメント スレッドに基づいて、この質問は Chrome が何をしているか、特に次のようにその動作を合理化することに重点を置いています。

function Foo(){};
Foo.prototype.constructor = function Bar(){};
f = new Foo();
console.log(f);              // => Foo{} (remembering that f created by Foo, ignoring constructor)
console.log(Foo.prototype)   // => Bar{} (reporting constructor value)

詳細については、 https://gist.github.com/getify/5793213を参照してください。

4

4 に答える 4

17

JavaScript のプロトタイプの継承は、非常にねじれた形をしています。私はこれをプロトタイプ継承のコンストラクター パターンと呼んでいます。原型継承には別のパターンもあります - 原型継承の原型パターンです。後者について先に説明します。

JavaScript では、オブジェクトはオブジェクトから継承されます。授業は必要ありません。これは良いことです。それは生活を楽にします。たとえば、線のクラスがあるとします。

class Line {
    int x1, y1, x2, y2;

    public:

    Line(int x1, int y1, int x2, int y2) {
        this.x1 = x1;
        this.y1 = y1;
        this.x2 = x2;
        this.y2 = y2;
    }

    int length() {
        int dx = x2 - x1;
        int dy = y2 - y1;
        return sqrt(dx * dx + dy * dy);
    }
}

はい、これは C++ です。クラスを作成したので、オブジェクトを作成します。

Line line1(0, 0, 0, 100);
Line line2(0, 100, 100, 100);
Line line3(100, 100, 100, 0);
Line line4(100, 0, 0, 0);

これらの 4 本の線は正方形を形成します。

JavaScript にはクラスがありません。プロトタイプの継承があります。プロトタイプ パターンを使用して同じことを行いたい場合は、次のようにします。

var line = {
    create: function (x1, y1, x2, y2) {
        var line = Object.create(this);
        line.x1 = x1;
        line.y1 = y1;
        line.x2 = x2;
        line.y2 = y2;
        return line;
    },
    length: function () {
        var dx = this.x2 - this.x1;
        var dy = this.y2 - this.y1;
        return Math.sqrt(dx * dx + dy * dy);
    }
};

line次に、次のようにオブジェクトのインスタンスを作成します。

var line1 = line.create(0, 0, 0, 100);
var line2 = line.create(0, 100, 100, 100);
var line3 = line.create(100, 100, 100, 0);
var line4 = line.create(100, 0, 0, 0);

それだけです。prototypeプロパティでコンストラクター関数を混乱させることはありません。継承に必要な唯一の関数はObject.create. この関数はオブジェクト (プロトタイプ) を受け取り、プロトタイプから継承する別のオブジェクトを返します。

残念ながら、Lua とは異なり、JavaScript はプロトタイプ継承のコンストラクター パターンをサポートしているため、プロトタイプ継承を理解するのがより難しくなっています。コンストラクター パターンは、プロトタイプ パターンの逆です。

  1. プロトタイプ パターンでは、オブジェクトが最も重要視されます。したがって、オブジェクトが他のオブジェクトから継承されていることが簡単にわかります。
  2. コンストラクター パターンでは、関数が最も重要視されます。したがって、人々はコンストラクターが他のコンストラクターから継承すると考える傾向があります。これは間違っています。

上記のプログラムは、コンストラクター パターンを使用して記述すると、次のようになります。

function Line(x1, y1, x2, y2) {
    this.x1 = x1;
    this.y1 = y1;
    this.x2 = x2;
    this.y2 = y2;
}

Line.prototype.length = function () {
    var dx = this.x2 - this.x1;
    var dy = this.y2 - this.y1;
    return Math.sqrt(dx * dx + dy * dy);
};

Line.prototype次のようにインスタンスを作成できるようになりました。

var line1 = new Line(0, 0, 0, 100);
var line2 = new Line(0, 100, 100, 100);
var line3 = new Line(100, 100, 100, 0);
var line4 = new Line(100, 0, 0, 0);

コンストラクター パターンとプロトタイプ パターンの類似点に注目してください。

  1. createプロトタイプのパターンでは、メソッドを持つオブジェクトを作成するだけです。コンストラクター パターンで関数を作成すると、JavaScript が自動的にprototypeオブジェクトを作成します。
  2. プロトタイプ パターンには、 と の 2 つのメソッドがcreateありlengthます。コンストラクター パターンにも、 と の 2 つのメソッドがconstructorありlengthます。

prototypeコンストラクター パターンはプロトタイプ パターンの逆です。これは、関数を作成すると JavaScriptが関数のオブジェクトを自動的に作成するためです。オブジェクトには、関数自体を指すprototypeというプロパティがあります。constructor

エリックが言ったように、console.log出力することがわかっている理由は、次のFooように渡すときFoo.prototypeですconsole.log

  1. Foo.prototype.constructorどれが自分自身であるかを見つけFooます。
  2. JavaScript のすべての名前付き関数には、 というプロパティがありますname
  3. したがってFoo.nameです"Foo"。したがって、 で文字列が見つかり"Foo"ますFoo.prototype.constructor.name

編集:わかりました。JavaScript でprototype.constructorプロパティを再定義する際に問題があることを理解しています。問題を理解するために、まずnew演算子がどのように機能するかを理解しましょう。

  1. まず、上で示した図をよく見てほしい。
  2. 上の図には、コンストラクター関数、プロトタイプ オブジェクト、およびインスタンスがあります。
  3. newコンストラクタ JS が新しいオブジェクトを作成する前に、キーワードを使用してインスタンスを作成する場合。
  4. この新しいオブジェクトの内部[[proto]]プロパティは、オブジェクトの作成時にconstructor.prototype指定されたものを指すように設定されます。

これは何を意味しますか?次のプログラムを検討してください。

function Foo() {}
function Bar() {}

var foo = new Foo;

Foo.prototype = Bar.prototype;

var bar = new Foo;

alert(foo.constructor.name); // Foo
alert(bar.constructor.name); // Bar

ここで出力を参照してください: http://jsfiddle.net/z6b8w/

  1. インスタンスfooは から継承しFoo.prototypeます。
  2. したがってfoo.constructor.name、 が表示されます"Foo"
  3. 次に、に設定Foo.prototypeBar.prototypeます。
  4. したがって、 によって作成されましたが、barから継承されます。Bar.prototypenew Foo
  5. bar.constructor.nameです"Bar"。_

あなたが提供したJSフィドルFooで、関数を作成してから次のように設定Foo.prototype.constructorしましたfunction Bar() {}

function Foo() {}
Foo.prototype.constructor = function Bar() {};
var f = new Foo;
console.log(f.hasOwnProperty("constructor"));
console.log(f.constructor);
console.log(f);

Foo.prototypeのすべてのインスタンスのプロパティを変更したため、Foo.prototypeこの変更が反映されます。したがってf.constructorですfunction Bar() {}f.constructor.nameそうではあり"Bar"ません"Foo"

自分の目で確かめてください -f.constructor.nameです"Bar"


Chrome は、そのような奇妙なことを行うことで知られています。理解しておくべき重要なことは、Chrome はデバッグ ユーティリティであり、console.log主にデバッグ目的で使用されるということです。

したがって、新しいインスタンスを作成すると、Chrome はおそらく元のコンストラクターを によってアクセスされる内部プロパティに記録しますconsole.log。したがってFoo、 ではなくを表示しますBar

これは実際の JavaScript の動作ではありません。プロパティを上書きするときの仕様によるとprototype.constructor、インスタンスと元のコンストラクターの間にリンクはありません。

他の JavaScript 実装 (Opera コンソール、node.js、RingoJS など) は正しいことを行い、Bar. したがって、Chrome の動作は非標準であり、ブラウザ固有のものであるため、慌てる必要はありません。

理解することが重要なのは、オブジェクトのプロパティのFoo代わりにChrome が表示されても、他の実装と同じであることです。Barconstructorfunction Bar() {}

于 2013-06-15T01:13:02.657 に答える
0

オンラインで少し掘り下げましたが、プロトタイプやその他の主要なコア javascript 機能がどのように機能するかを実際に示している次の記事を見つけました。

http://dmitrysoshnikov.com/ecmascript/javascript-the-core/

プロトタイプ チェーンがどのように見えるかを示す図が特に気に入っています。

于 2014-10-27T23:59:14.483 に答える