JavaScriptの理解(四)

28910 ワード

第四編はずいぶん長引いて、本当に申し訳ありません.正直に言うと、長引く原因は主にどう書くか考えていないことです.このテーマには挑戦性があるからです.原型と原型ベースの継承--あ~やっと言いました.これで遅らせる口実はありません.==

プロトタイプ


私(個人)が嫌いなのは、原型を話すときに上がってきて類を比較することなので、そうは言いません.しかし、私は確かにコンストラクタ関数を話したことがありますが、この点ではクラスと多少共通点がありません.私の提案は、クラスを忘れることです.「類」学の氾濫は対象への過度な発展であり、悲しみであり、多くの開発者がほとんど対象と類に等号をつけているという見方が多い.原型を学ぶ前に、この言葉を覚えて味わってください.
オブジェクト向け設計の真髄は「抽象」の二字にあり、クラスは実体抽象を実現する手段であるが、唯一ではない.

prototypeと_proto__


事前に宣言します:永遠に、真実のコードの中で__proto__の属性を使わないでください、本文の中でそれを使って純粋に研究に使います!すぐに代替品についてお話ししますので、申し訳ありませんが我慢してください.
JavaScriptでは、関数が対象になります(これを学んだら、関数がどのように対象になったのかを検討してみてはいかがでしょうか).オブジェクトは、意外にも属性があり(方法も属性)、そして意外にもprototypeが関数の属性であり、最後に意外なprototypeもオブジェクトである.ほら、どんなに順調なことか.

  
  
  
  
function foo() {} foo.prototype; //
prototypeは何の役に立つの?ええ、関数を関数として使うと、全然役に立ちません.ただし、関数をコンストラクタとして使用すると、新しく生成されたオブジェクトはprototypeオブジェクトのプロパティに直接アクセスできます.

  
  
  
  
// , function Foo() {} var f = new Foo(); f.constructor; // function Foo() {}

考えてみてください.fconstructorの属性はどこから来ましたか.もしあなたが分からないなら、console.dir(Foo.prototype)で真相を探してください.
これは問題を示しています.
関数のプロトタイププロパティは、関数自体ではなく、コンストラクタとして作成されたオブジェクトに使用されます.
疑問なことに、prototype属性はFoo関数オブジェクト内に存在するが、Fooによって作成されたインスタンスオブジェクトfは、prototypeにどのようにアクセスするのか.prototypeオブジェクトをコピーしますか?次のコードを見てみましょう.

  
  
  
  
f.__proto__; // Foo {} Foo.prototype; // Foo {} f.__proto__ === Foo.prototype; // true

ああ~コピーしたのではなく、__proto__という属性がコンストラクタのprototypeオブジェクトを指していますね.
間違いない!これがプロトタイプメカニズムの真髄です.すべての詳細(表象の下に隠されているものを含む)をまとめましょう.
関数にはprototypeのプロパティがありますが、関数自体はを使用しません.
関数がコンストラクタとして機能する場合、newオペレータの組み合わせが必要な新しいオブジェクトを作成できます.その仕事の原理は私はすでに第1編で大部分の述べをしましたまだ言及していないのは、newが新しいオブジェクトを作成するときに、コンストラクタを指すprototypeのプロパティを新しいオブジェクトに与えます.この新しいプロパティは、いくつかのブラウザ環境では__proto__ と呼ばれています.
オブジェクトのプロパティ(メソッドを含む)にアクセスすると、まず、そのオブジェクト自体にそのプロパティがあるかどうかを検索し、ない場合はそのプロトタイプ(つまり__proto__が指すprototypeオブジェクト)、ない場合はプロトタイプのプロトタイプ(prototypeも独自の__proto__もあり、より上位のprototypeオブジェクトを指す)を検索し、Objectが見つかるまでを検索する.
OK、上の4つ目は事実上JavaScriptのオブジェクト属性検索メカニズムです.以下のようになります.
プロトタイプの意味は、オブジェクトのプロパティ検索メカニズムに方向を提供すること、またはルートを提供することです.
1つのオブジェクトで、多くのプロパティがあり、1つのプロパティが別のオブジェクトのプロトタイププロパティを指します.後者には、別のオブジェクトのプロトタイプ属性を指す属性もあります.これは1つの輪に1つの輪の鎖のように、この鎖のいずれかの点から探して、最後にチェーンの起点、すなわちObjectを見つけることができます.したがって,このメカニズムをプロトタイプチェーンと呼ぶ.
次に、使用する用語(少なくとも本明細書の範囲内)を統一したいと思います.
関数のprototypeプロパティ:プロトタイププロパティまたはプロトタイプオブジェクトと呼びます.
オブジェクトの__proto__プロパティ:プロトタイプと呼びます.
例:Fooのプロトタイプ属性(またはプロトタイプオブジェクト)=Foo.prototype fのプロトタイプ=f.__proto__ 統一用語は、Foo.prototypeおよびf.__proto__が等価であるにもかかわらず、prototypeおよび__proto__が異なるためである.固定されたオブジェクトを考慮すると、prototypeはプロトタイプチェーンの下に使用され、__proto__はプロトタイプチェーンの上を指す.したがって,いったん「プロトタイプ属性」や「プロトタイプオブジェクト」と言うと,これは子孫や孫たちに与えられたものであることを暗示し,「プロトタイプ」というのは親世代から受け継がれたものであることを示唆している.
言い換えれば、オブジェクトのプロトタイプ属性やプロトタイプオブジェクトは自分用ではなく、オブジェクトのプロトタイプは直接使用できます.

__proto__ の質問

__proto__がオブジェクトのプロトタイプにアクセスできる以上、なぜ実際に使用できないのでしょうか.
これは設計上のミスであり、__proto__の属性が修正可能であると同時に、JavaScriptの属性検索メカニズムが「マヒ」することを意味するため、それを強く推奨しない.
オブジェクトを介してプロトタイプにアクセスする場合、ES 5は新しい方法を提供します.

  
  
  
  
Object.getPrototypeOf(f) // Foo {}

これは安全ですから、安心して使ってください.低バージョンブラウザの互換性の問題を考慮してes 5-shimを使用できます

固有属性とプロトタイプ属性の違い


オブジェクトのプロトタイプは、割り当てではなく参照であるため、プロトタイプのプロパティを変更すると、すべてのインスタンスオブジェクトにすぐに作用します.このプロパティは、オブジェクトのインスタンスメソッドを定義するのに最適です.

  
  
  
  
function Person(name) { this.name = name; } Person.prototype.greeting = function () { return " , " + this.name; }; var p1 = new Person(" "); var p2 = new Person(" "); p1.greeting(); // , p2.greeting(); // , /* :*/ Person.prototype.greeting = function () { return " , " + this.name + ", !"; }; /* :*/ p1.greeting(); // , , ! p2.greeting(); // , , !

ただし、独自のプロパティを変更すると、新しく作成したインスタンスオブジェクトにのみ影響を与えます.

  
  
  
  
function Person(name) { this.name = " "; } /* */ p1.greeting(); // , , ! /* */ var p3 = new Person(" "); p3.greeting(); // , , !

この例は少し無厘頭に見え、あまり役に立たないが、現実世界では複雑なオブジェクトの行為が状況に応じて書き換えられるかもしれないが、オブジェクトの内部状態を変えたくないという精神がある.あるいは、親オブジェクトのいくつかの動作を他の同じ部分に導かずに上書きする継承を実現します.これらの場合、プロトタイプは私たちに最大限の柔軟性を与えます.
属性が独自であるか、プロトタイプから来ているかをどのように知るか.上コード~

  
  
  
  
p1.hasOwnProperty("name"); // true p1.hasOwnProperty("greeting"); // false p1.constructor.prototype.hasOwnProperty("greeting"); // true Object.getPrototypeOf(p1).hasOwnProperty("greeting"); // true

コードは簡単で、過度に説明する必要はありません.最後の2つの文は実際に等価な書き方に注意してください.

気をつけて


さっきのこのコード:p1.constructor.prototype.hasOwnProperty("greeting");、実は面白い問題が隠されています.
オブジェクトp1は、constructorのプロパティを提供してくれたプロトタイプに感謝して、独自のコンストラクタにアクセスできます.次に、constructorプロパティを使用して、プロトタイプオブジェクトに逆アクセスできます.これは輪のようです.試してみましょう.

  
  
  
  
p1.constructor === p1.constructor.prototype.constructor; // true p1.constructor === p1.constructor.prototype.constructor.prototype.constructor; // true

本当に!しかし、私たちは面白いからこれを研究したわけではありません.
プロトタイプオブジェクトのプロパティを変更すると、すぐにすべてのインスタンスオブジェクトに作用しますが、プロトタイプオブジェクトを完全に上書きすると、奇妙なことになります.(次の例を読んで、自分の考えを一言一言検証してください)

  
  
  
  
function Person(name) { this.name = name; } var p1 = new Person(" "); Person.prototype.greeting = function () { return " , " + this.name; }; p1.name; // p1.greeting(); // , p1.constructor === Person; // true /* so far so good, but... */ Person.prototype = { say: function () { return " , " + this.name; } }; p1.say(); // TypeError: Object #<Person> has no method 'say' p1.constructor.prototype; // Object { say: function }

え?Personのプロトタイプ属性にはsayの方法があるのに?プロトタイプオブジェクトは即時に有効ではありませんか?

プロトタイプ継承


1つのオブジェクトを作成するだけでは、プロトタイプの役割はすべて発揮されません.プロトタイプとプロトタイプチェーンの特性をさらに利用して、私たちのコードを拡張し、プロトタイプベースの継承を実現します.
プロトタイプ継承は非常に大きな話題の範囲であり、プロトタイプ継承はクラス継承ほど整っていないように見えますが(相対的に)、より柔軟であることが徐々にわかります.単一継承でも多重継承でも、Mixinや他の名前さえ言えない継承方式でも、原型継承は実現する方法があり、一つの方法ではないことが多い.
まず簡単なことから始めましょう

  
  
  
  
function Person() { this.klass = ' '; } Person.prototype.toString = function () { return this.klass; }; Person.prototype.greeting = function () { return ' , ' + this.name + ', ' + this.klass + '。'; }; function Programmer(name) { this.name = name; this.klass = ' '; } Programmer.prototype = new Person(); Programmer.prototype.constructor = Programmer;

これは非常に良い例で、以下のポイントを示しています.

  
  
  
  
var someone = new Programmer(' '); someone.name; // someone.toString(); // someone.greeting(); // ‌ , , 。

私が撫でてみます.
最後から2番目の行、new Person()はオブジェクトを作成し、Programmer.prototypeに割り当てられ、コンストラクタのプロトタイプ属性はPersonのインスタンスオブジェクトになります. Personオブジェクトは書き換えられたtoString()メソッドを有し、このメソッドは宿主オブジェクトのklassプロパティを返すので、Programmergreeting()メソッドを定義し、継承されたtoString()を使用することができる. someoneオブジェクトがtoString()メソッドを呼び出すと、thisはそれ自体を指すので、 ではなく を出力することができる.
まだ終わっていないので、続けてみます.

  
  
  
  
// Programmer.prototype.constructor = Programmer; : someone.constructor === Programmer; ‌// true // “ ” ‌‌someone instanceof Programmer; ‌// true ‌‌someone instanceof Person; //‌ true ‌‌someone instanceof Object; ‌// true

メソッドの再ロード


上記の例では、toString()メソッドのリロード(このメソッドの始祖オブジェクトはObject.prototype)が実現されています.同じ精神を持って、私たち自身が書いたサブコンストラクタも、プロトタイプ属性によって親コンストラクタが提供するメソッドをリロードすることができます.

  
  
  
  
Programmer.prototype.toString = function () { return this.klass + "( )"; } var codingFarmer = new Programmer(" "); codingFarmer.greeting(); // , , ( )。

プロパティ検索とメソッドの再ロードの矛盾


思考が活発で反応が速い学生はもう考えているかもしれません.
親のプロトタイプ属性ではなく、親のインスタンスを子のプロトタイプ属性に割り当てる必要があるのはなぜですか.
いい質問だ!この考え方は非常に理にかなっており,これにより属性検索の回数を減らすことができる.上を検索するときに親インスタンスの__proto__をスキップし,(前例のように)Person.prototypeを直接見つけたからである.
しかし、そうしない理由も簡単です.もしあなたがそうしたら:

  
  
  
  
Programmer.prototype = Person.prototype;

Javascriptは参照付与値であるため、等号の両端の2つの属性が同じオブジェクトを指すことに等しいため、サブクラスでメソッドをリロードすると、親のメソッドも一緒に変化し、リロードの意味がなくなります.したがって、リロードが不要であることが確認された場合にのみ可能です.