導入
この章は、ECMAScriptオブジェクト指向の実装に関する第2章です。最初の章では、紹介とceMaScriptの比較について説明します。この章に進む前に最初の章を読んでいない場合は、この章が長すぎるため、最初に最初の章を読むことを強くお勧めします(35ページ)。
元の英語のテキスト:http://dmitrysoshhnikov.com/ecmascript/chapter-7-2-oop-ecmascript-implementation/
注:記事は長すぎるため、エラーは避けられず、常に修正されています。
はじめに、ECMAScriptに拡張します。さて、それがOOP実装であることがわかったら、それを正確に定義しましょう。
コードコピーは次のとおりです。
ECMAScriptは、プロトタイプに基づいて継承を委任することをサポートするオブジェクト指向のプログラミング言語です。
ECMAScriptは、プロトタイプベースの委任された継承をサポートするオブジェクト指向言語です。
最も基本的なデータ型を分析します。最初に理解する必要があるのは、ECMAScriptがプリミティブ値とオブジェクトを使用してエンティティを区別することです。したがって、「JavaScriptでは、すべてがオブジェクトです」では、いくつかの記事で言及されています(完全に正しいことではありません)、プリミティブ値はここで説明するデータ型です。
データ型
ECMaScriptは、タイプを動的に変換できる動的な弱いタイプの言語ですが、それでもデータ型があります。言い換えれば、オブジェクトは実際のタイプに属している必要があります。
標準仕様で定義されている9つのデータ型がありますが、ECMAScriptプログラムで直接アクセスできるのは6つだけです。それらは、未定義、ヌル、ブール、文字列、数字、およびオブジェクトです。
他の3つのタイプは、実装レベルでのみアクセスできます(これらのタイプはECMAScriptオブジェクトでは使用できません)。仕様に使用されて、運用動作を説明し、中間値を保存します。これらの3つのタイプは、参照、リスト、および完了です。
したがって、参照は、削除、typeof、これなどの演算子を説明するために使用され、ベースオブジェクトと属性名が含まれています。リストには、パラメーターリストの動作について説明します(新しい式と関数呼び出しの場合)。完了は、動作の破損、継続、返品、およびスローステートメントを説明するために使用されます。
プリミティブ値タイプ
6のECMAScriptプログラムで使用されているデータ型を振り返ってみると、最初の5つは、未定義、null、boolean、string、number、objectを含む原始的な値タイプです。
元の値の例:
コードコピーは次のとおりです。
var a =未定義;
var b = null;
var c = true;
var d = 'test';
var e = 10;
これらの値は最下層に直接実装され、オブジェクトではないため、プロトタイプもコンストラクターもありません。
おじさんのメモ:これらのネイティブ値は、私たちが通常使用するもの(ブール、文字列、数字、オブジェクト)に似ていますが、同じものではありません。したがって、typeof(boolean)の結果が関数であるため、typeof(true)とtypeof(boolean)の結果は異なるため、boolean、string、numberにはプロトタイプがあります(次の読み取り属性の章も言及されます)。
どのタイプのデータが最適かを知りたい場合は、TYPEOFを使用するのが最善です。注意する必要がある例があります。 typeofを使用してnullの種類を判断する場合、結果はオブジェクトです。なぜ? nullのタイプはnullとして定義されるためです。
コードコピーは次のとおりです。
アラート(typeof null); // "物体"
「オブジェクト」を表示する理由は、仕様がNULL値の型文字列値に対して「オブジェクトを返す」を規定するためです。
仕様ではこれを説明することは想像しませんが、ブレンダン・アイヒ(JavaScriptの発明者)は、NULLが主にオブジェクトが定義されていないことに関連して表示されることに気付きました。ただし、一部のドキュメントには、バグに起因する迷惑なドキュメントがあり、Brendan Eichも議論に参加したバグリストにバグを入れます。その結果、手放すと、typeof nullの結果をオブジェクトに設定する必要があります(262-3標準はnullのタイプをNULLとして定義し、262-5は標準をオブジェクトとしてnullに変更しました)。
オブジェクトタイプ
次に、オブジェクトタイプ(オブジェクトコンストラクターと混同しないように、抽象型のみが説明されている)は、ECMAScriptオブジェクトを記述する唯一のデータ型です。
オブジェクトは、キー価値のペアの順序付けられていないコレクションです。
オブジェクトは、キー価値のペアを含む順序付けられていないコレクションです
オブジェクトの重要な値はプロパティと呼ばれ、プロパティは元の値と他のオブジェクトのコンテナです。プロパティの値が関数である場合、それをメソッドと呼びます。
例えば:
コードコピーは次のとおりです。
var x = {//オブジェクト "x"には3つのプロパティがあります:a、b、c
A:10、//元の値
b:{z:100}、//オブジェクト "b"には属性zがあります
c:function(){// function(method)
アラート( 'メソッドX.C');
}
};
アラート(xa); // 10
アラート(xb); // [オブジェクトオブジェクト]
アラート(xbz); // 100
xc(); // 'メソッドX.C'
動的
第17章で指摘したように、ESのオブジェクトは完全に動的です。これは、プログラムが実行されたときに、オブジェクトのプロパティを自由に追加、変更、または削除できることを意味します。
例えば:
コードコピーは次のとおりです。
var foo = {x:10};
//新しい属性を追加します
foo.y = 20;
console.log(foo); // {x:10、y:20}
//プロパティ値を関数に変更します
foo.x = function(){
console.log( 'foo.x');
};
foo.x(); // 'foo.x'
//属性を削除します
foo.xを削除します。
console.log(foo); // {y:20}
一部のプロパティは変更できません - (読み取り専用プロパティ、削除されたプロパティ、または執政できないプロパティ)。属性の特性で後で説明します。
さらに、ES5仕様は、静的オブジェクトが新しい属性を拡張できず、プロパティページを削除または変更できないことを規定しています。それらはいわゆる凍結オブジェクトであり、Object.Freeze(O)メソッドを適用することで取得できます。
コードコピーは次のとおりです。
var foo = {x:10};
//オブジェクトをフリーズします
object.freeze(foo);
console.log(object.isfrozen(foo)); // 真実
//変更できません
foo.x = 100;
//拡張できません
foo.y = 200;
//削除できません
foo.xを削除します。
console.log(foo); // {x:10}
ES5仕様では、object.preventextensions(o)メソッドも拡張機能を防ぐために使用されます。または、Object.defineProperty(o)メソッドは、プロパティを定義するために使用されます。
コードコピーは次のとおりです。
var foo = {x:10};
object.defineProperty(foo、 "y"、{
値:20、
書き込み:false、//読み取り専用
設定可能:false //構成できません
});
//変更できません
foo.y = 200;
//削除できません
foo.yを削除します; // 間違い
//予防および制御拡張
object.preventextensions(foo);
console.log(object.isextensible(foo)); // 間違い
//新しい属性を追加できません
foo.z = 30;
console.log(foo); {x:10、y:20}
組み込みオブジェクト、ネイティブオブジェクト、ホストオブジェクト
仕様は、これらの組み込みオブジェクト、要素オブジェクト、ホストオブジェクトを区別することにも注意する必要があります。
内蔵オブジェクトと要素オブジェクトは、ECMAScript仕様によって定義および実装されており、2つの違いは些細なことです。すべてのECMAScript実装オブジェクトはネイティブオブジェクトです(一部は組み込みオブジェクトです。一部はユーザー定義のオブジェクトなど、プログラムが実行されるときに作成されます)。内蔵オブジェクトは、ネイティブオブジェクトのサブセットであり、プログラムが開始される前にECMAScriptに組み込まれています(たとえば、Parseint、Matchなど)。すべてのホストオブジェクトは、通常はブラウザであるホスト環境によって提供され、たとえばウィンドウ、アラートなどを含む場合があります。
ホストオブジェクトはES自体によって実装され、規範的なセマンティクスに完全に適合する場合があることに注意してください。この点で、それらは「ネイティブホスト」オブジェクトと呼ぶことができます(できるだけ早く)が、標準は「ネイティブホスト」オブジェクトの概念を定義しません。
ブール、文字列、および数値オブジェクト
さらに、仕様では、いくつかのネイティブの特別なラッパークラスも定義されています。これらのオブジェクトは次のとおりです。
1。ブールオブジェクト
2。文字列オブジェクト
3。デジタルオブジェクト
これらのオブジェクトは、対応する内蔵コンストラクターによって作成され、内部プロパティとしてネイティブ値が含まれています。これらのオブジェクトは、元の値を保存するために変換でき、その逆も同様です。
コードコピーは次のとおりです。
var c = new boolean(true);
var d = new String( 'test');
var e = new Number(10);
//元の値に変換します
//新しいキーワードなしで関数を使用します
с= boolean(c);
d =文字列(d);
e = number(e);
//オブジェクトに再び転換します
с= object(c);
d = object(d);
e = object(e);
さらに、特別な内蔵コンストラクターによって作成されたオブジェクトがあります:関数(関数オブジェクトコンストラクター)、アレイ(アレイコンストラクター)、Regexp(正規表現コンストラクター)、数学(数学モジュール)、日付(日付コンストラクター)などがあります。これらのオブジェクトはオブジェクトオブジェクトタイプの値でもあります。それらの違いは、内部プロパティによって管理されます。これらのことについて以下で説明します。
リテラル
オブジェクト、配列、および正規表現の3つのオブジェクトの値について、それらはオブジェクト初期イザー、アレイイニシャルイザー、および正規表現初期化と呼ばれる略語識別子を持っています。
コードコピーは次のとおりです。
//新しい配列に相当する(1、2、3);
//またはarray = new Array();
//配列[0] = 1;
//配列[1] = 2;
//配列[2] = 3;
var array = [1、2、3];
//に相当します
// var object = new object();
// object.a = 1;
// object.b = 2;
// object.c = 3;
var object = {a:1、b:2、c:3};
// new regexpに相当( "^// d+$"、 "g")
var re =/^/d+$/g;
上記の3つのオブジェクトが新しいタイプに再割り当てされている場合、その後の実装セマンティクスは新しいタイプに従って使用されることに注意してください。たとえば、現在のRhinoとSpidermonkey 1.7の古いバージョンでは、オブジェクトは新しいキーワードのコンストラクターで正常に作成されますが、いくつかの実装(現在のSpider/Tracemonkey)のセマンティクスは、タイプが変更された後に変更されない場合があります。
コードコピーは次のとおりです。
var getclass = object.prototype.tostring;
object = number;
var foo = new Object;
アラート([foo、getclass.call(foo)]); // 0、「[オブジェクト番号]」
var bar = {};
// Rhino、Spidermonkey 1.7- 0、「[オブジェクト番号]」
//その他:まだ「[オブジェクトオブジェクト]」、「[オブジェクトオブジェクト]」
アラート([bar、getclass.call(bar)]);
//配列には同じ効果があります
array = number;
foo = new Array;
アラート([foo、getclass.call(foo)]); // 0、「[オブジェクト番号]」
bar = [];
// Rhino、Spidermonkey 1.7- 0、「[オブジェクト番号]」
//その他:Still ""、 "[Object object]"
アラート([bar、getclass.call(bar)]);
//しかし、regexpの場合、リテラルのセマンティクスは変更されません。文字通りのセマンティクス
//すべてのテストされた実装で変更されていません
regexp = number;
foo = new regexp;
アラート([foo、getclass.call(foo)]); // 0、「[オブジェクト番号]」
bar = /(?!) /g;
アラート([bar、getclass.call(bar)]); ///(?!)/g、 "[object regexp]"
正規表現リテラルと正規表現性オブジェクト
次の2つの例では、第3版の仕様では、正規表現のセマンティクスが同等であることに注意してください。 regexpリテラルは1つの文にのみ存在し、解析段階に作成されます。ただし、regexpコンストラクターは新しいオブジェクトを作成するため、テスト中にlastIndex値が間違っているなど、いくつかの問題を引き起こす可能性があります。
コードコピーは次のとおりです。
for(var k = 0; k <4; k ++){
var re = /ecma /g;
アラート(re.lastindex); // 0、4、0、4
アラート(re.test( "ecmascript")); // true、false、true、false
}
// 比較する
for(var k = 0; k <4; k ++){
var re = new regexp( "ecma"、 "g");
アラート(re.lastindex); // 0、0、0、0
アラート(re.test( "ecmascript")); // true、true、true、true
}
注:ただし、これらの問題は、第5版のES仕様で修正されています。リテラルまたはコンストラクターに基づいているかどうかに関係なく、新しいオブジェクトを作成します。
連想配列
テキストのさまざまな静的ディスカッション、JavaScriptオブジェクト(多くの場合オブジェクト初期イザー{}で作成された)は、ハッシュテーブルとハッシュテーブルまたはその他の単純なタイトルと呼ばれます:ハッシュ(RubyまたはPerlの概念)、管理アレイ(PHPの概念)、辞書(Pythonの概念)など。
そのような用語のみがあります。主に、それらの構造が類似しているためです。つまり、「キー値」ペアを使用してオブジェクトを保存します。オブジェクトは、「アソシエーションアレイ」または「ハッシュテーブル」理論によって定義されたデータ構造に完全に適合しています。さらに、ハッシュテーブルの要約データ型は通常、実装レベルで使用されます。
ただし、概念は用語の観点から説明されていますが、これは実際には間違いです。 ECMAScriptの観点からは、ECMAScriptには1つのオブジェクト、タイプとそのサブタイプのみがあります。これは、「キー価値」とストレージまで違いはないため、これに特別な概念はありません。任意のオブジェクトの内部プロパティは、キー価値の「ペア」として保存できるためです。
コードコピーは次のとおりです。
var a = {x:10};
a ['y'] = 20;
AZ = 30;
var b = new Number(1);
BX = 10;
by = 20;
b ['z'] = 30;
var c = new function( '');
cx = 10;
Cy = 20;
c ['z'] = 30;
//など、任意のオブジェクトのサブタイプ「サブタイプ」
さらに、ECMAScriptでオブジェクトは空になる可能性があるため、「ハッシュ」の概念もここでは間違っています。
コードコピーは次のとおりです。
object.prototype.x = 10;
var a = {}; //空の「ハッシュ」を作成する
アラート(a ["x"]); // 10ですが、空ではありません
アラート(a.toString); // 関数
a ["y"] = 20; //「ハッシュ」に新しいキー値ペアを追加する
アラート(a ["y"]); // 20
object.prototype.y = 20; //プロトタイプ属性を追加します
["y"]を削除します。 // 消去
アラート(a ["y"]); //しかし、ここでキーと価値はまだ20の価値があります
ES5標準では、非プロトタイプのオブジェクト(Object.create(null)メソッドを使用して実装)を作成できることに注意してください。この観点から、そのようなオブジェクトはハッシュテーブルと呼ぶことができます。
コードコピーは次のとおりです。
var ahashtable = object.create(null);
console.log(ahashtable.tostring); // 未定義
さらに、一部のプロパティには特定のゲッター/セッターメソッドがあるため、この概念に関する混乱につながる可能性があります。
コードコピーは次のとおりです。
var a = new String( "foo");
a ['length'] = 10;
アラート(a ['length']); // 3
ただし、「ハッシュ」に「プロトタイプ」(たとえば、RubyまたはPythonのハッシュオブジェクトを委任するクラス)がある場合でも、2つの表記(つまり、DOT表記ABとA [b "]表記)の間に意味的な違いがないため、この用語はECMAScriptで正しくありません。
ECMAScriptの「プロパティ属性」の概念は、「キー」、配列インデックス、およびメソッドから意味的に分離されていません。ここでは、オブジェクトのすべての属性の読み取りと書き込みは、統一されたルールに従う必要があります。プロトタイプチェーンを確認してください。
次のRubyの例では、セマンティックの違いがわかります。
コードコピーは次のとおりです。
a = {}
A.クラス#ハッシュ
A.Length#0
#新しい "key-value"ペア
a ['length'] = 10;
#意味的には、キーではなく、ポイントでアクセスされる属性またはメソッド
A.Length#1
#インデクサーはハッシュのキーにアクセスします
a ['length']#10
#既存のオブジェクトでハッシュクラスを動的に宣言することに似ています
#次に、新しい属性またはメソッドを宣言します
クラスハッシュ
def z
100
終わり
終わり
#新しいプロパティにアクセスできます
AZ#100
#しかし、「キー」ではありません
a ['z']#nil
ECMA-262-3標準は、「ハッシュ」の概念(および同様)を定義していません。ただし、そのような構造理論がある場合、これにちなんで名付けられたオブジェクトはそうかもしれません。
オブジェクト変換
オブジェクトをプリミティブ値に変換するには、ValueOfメソッドを使用できます。私たちが言ったように、関数のコンストラクターが関数(一部のタイプの場合)と呼ばれますが、新しいキーワードを使用しない場合、オブジェクトを元の値に変換すると、メソッドの暗黙の値の呼び出しに相当します。
コードコピーは次のとおりです。
var a = new Number(1);
var primitivea = number(a); //暗黙の「Valueof」コール
var alsoprimitivea = a.valueof(); //明示的な呼び出し
アラート([
typeof a、// "object"
Primitiveaの種類、//「番号」
typeof alsoprimitivea // "number"
]);
この方法により、オブジェクトは次のようなさまざまな操作に参加できます。
コードコピーは次のとおりです。
var a = new Number(1);
var b = new Number(2);
アラート(a + b); // 3
// 平
var c = {
X:10、
Y:20、
valueof:function(){
this.x + this.yを返します。
}
};
var d = {
X:30、
Y:40、
// CのValueofと同じ関数
valueof:c.valueof
};
アラート(C + D); // 100
ValueOFのデフォルト値は、オブジェクトのタイプ(オーバーライドされていない場合)に応じて変更されます。一部のオブジェクトの場合、これを返します-Object.Prototype.Valueof()、および計算値:date.prototype.valueof()returns date and time:
コードコピーは次のとおりです。
var a = {};
alert(a.valueof()=== a); // true、「valueof」はこれを返します
var d = new date();
alert(d.valueof()); // 時間
alert(d.valueof()=== d.getTime()); // 真実
さらに、オブジェクトには、より原始的な表現 - 文字列ディスプレイがあります。このトストリング方法は信頼性が高く、一部の操作で自動的に使用されます。
コードコピーは次のとおりです。
var a = {
valueof:function(){
100を返します。
}、
toString:function(){
'__test'を返します。
}
};
//この操作では、ToStringメソッドが自動的に呼び出されます
アラート(a); // "__テスト"
//しかし、ここでは、valueof()メソッドが呼び出されます
アラート(a + 10); // 110
//ただし、valueofが削除されると
// ToStringは再び自動的に呼び出すことができます
a.valueofを削除します。
アラート(a + 10); // "_test10"
Object.Prototypeで定義されたToStringメソッドは特に重要であり、以下で説明する内部[クラス]属性値を返します。
元の値(トップリミティブ)への変換と比較して、値をオブジェクトタイプに変換する変換仕様(toobject)もあります。
明示的な方法は、組み込みのオブジェクトコンストラクターをあまりにも起源に呼び出す関数として使用することです(新しいキーワードを使用するようなものは大丈夫です):
コードコピーは次のとおりです。
var n = object(1); // [オブジェクト番号]
var s = object( 'test'); // [オブジェクト文字列]
//新しいオペレーターでもいくつかの類似点も可能です
var b = new Object(true); // [Object Boolean]
//パラメーターの新しいオブジェクトを適用すると、単純なオブジェクトを作成します
var o = new object(); // [オブジェクトオブジェクト]
//パラメーターが既存のオブジェクトである場合
//作成の結果は、単にオブジェクトを返すことです
var a = [];
アラート(a === new Object(a)); // 真実
アラート(a ===オブジェクト(a)); // 真実
組み込みのコンストラクターの呼び出しに関して、コンストラクターに応じて、新しいオペレーターを使用または適用しないことに関する一般的なルールはありません。たとえば、配列または関数は、新しいオペレーターのコンストラクターまたは新しいオペレーターを使用して同じ結果を生成しない単純な関数を使用します。
コードコピーは次のとおりです。
var a = array(1、2、3); // [オブジェクト配列]
var b = new Array(1、2、3); // [オブジェクト配列]
var c = [1、2、3]; // [オブジェクト配列]
var d = function( ''); // [オブジェクト関数]
var e = new function( ''); // [オブジェクト関数]
一部のオペレーターを使用すると、表示と暗黙の変換もあります。
コードコピーは次のとおりです。
var a = 1;
var b = 2;
//暗黙的
var c = a + b; // 3、番号
var d = a + b + '5' // "35"、文字列
//明示的
var e = '10'; // "10"、文字列
var f = +e; // 10、番号
var g = parseint(e、10); // 10、番号
//など
属性のプロパティ
すべてのプロパティには多くの属性があります。
1. {readonly} - 値を属性に割り当てる書き込み操作を無視しますが、読み取り専用属性はホスト環境の動作によって変更される可能性があります。つまり、「一定の値」ではありません。
2. {dontenum} - 属性は、for .. in loopで列挙できません
3. {dontdelete} - 削除演算子の動作は無視されます(つまり、削除することはできません)。
4. {内部} - 内部属性、名前なし(実装レベルでのみ使用)、そのような属性にECMAScriptでアクセスできません。
es5 {readonly}、{dontenum}、{dontdelete}では、[[writable]]、[enumerable]]、[[configurable]]に改名され、これらのプロパティはobject.definepropertyまたは同様の方法を介して手動で管理できることに注意してください。
コードコピーは次のとおりです。
var foo = {};
object.defineProperty(foo、 "x"、{
値:10、
書き込み:true、// i.e. {readonly} = false
列挙可能:false、// i.e. {dontenum} = true
構成可能:true // i.e. {dontdelete} = false
});
console.log(foo.x); // 10
//説明から属性を取得します
var desc = object.getownPropertyDescriptor(foo、 "x");
console.log(desc.Numerable); // 間違い
console.log(desc.writable); // 真実
//など
内部プロパティと方法
オブジェクトは内部プロパティ(実装レベルの一部)を持つこともでき、ECMAScriptプログラムは直接アクセスできません(ただし、いくつかの実装により、そのようなプロパティへのアクセスが可能になります)。これらのプロパティは、ネストされた括弧[[]]からアクセスされます。これらのプロパティのいくつかを見てみましょう。これらのプロパティの説明は、仕様に記載されています。
各オブジェクトは、次の内部プロパティと方法を実装する必要があります。
1。[[プロトタイプ]] - オブジェクトのプロトタイプ(以下で詳しく紹介されます)
2。[[class]] - 文字列オブジェクトの表現(たとえば、オブジェクト配列、関数オブジェクト、関数など);オブジェクトを区別するために使用されます
3。[[get]] - 属性値を取得する方法
4。[[put]] - 属性値を設定する方法
5。[[Canput]] - 属性が書き込み可能かどうかを確認します
6
7。[[削除]] - オブジェクトからこのプロパティを削除します
8。[[defaultValue]]オブジェクトの元の値を返します(値の値を呼び出し、一部のオブジェクトはtypeRror例外をスローする場合があります)。
内部プロパティの値[[class]]は、object.prototype.tostring()メソッドを介して間接的に取得できます。これは、次の文字列を返す必要があります。[Object " + [[class]] +"] "。例えば:
コードコピーは次のとおりです。
var getclass = object.prototype.tostring;
getclass.call({}); // [オブジェクトオブジェクト]
getclass.call([]); // [オブジェクト配列]
getclass.call(new Number(1)); // [オブジェクト番号]
//など
この関数は通常、オブジェクトをチェックするために使用されますが、仕様では、ホストオブジェクトの[[class]]は、組み込みオブジェクトの[[class]]属性の値を含む任意の値になる可能性があるため、理論的には100%正確にすることはできません。たとえば、document.childnodes.item(...)メソッドの[[class]]プロパティはIEで「文字列」を返しますが、他の実装では返された「関数」は実際にです。
コードコピーは次のとおりです。
// IE-「文字列」、他の - 「関数」
Alert(getClass.Call(document.childnodes.item));
コンストラクタ
したがって、上で述べたように、ECMAScriptのオブジェクトはいわゆるコンストラクターを介して作成されます。
コンストラクターは、新しく作成されたオブジェクトを作成および初期化する関数です。
コンストラクターは、新しく作成されたオブジェクトを作成および初期化する関数です。
オブジェクトの作成(メモリ割り当て)は、コンストラクター[[コンストラクト]]の内部方法の責任です。この内部メソッドの動作が定義され、すべてのコンストラクターがこの方法を使用して新しいオブジェクトにメモリを割り当てます。
初期化は、コンストラクターの内部方法[[call]]によって責任を負う新しいオブジェクトの上下に関数を呼び出すことによって管理されます。
ユーザーコードは初期化フェーズでのみアクセスできることに注意してください。ただし、初期化フェーズ中に異なるオブジェクトを返すことができます(最初のフェーズで作成されたTIHSオブジェクトを無視してください):
コードコピーは次のとおりです。
関数a(){
//新しく作成されたオブジェクトを更新します
this.x = 10;
//しかし、返品は別のオブジェクトです
return [1、2、3];
}
var a = new a();
console.log(ax、a);未定義、[1、2、3]
第15章関数を参照 - 機能を作成するためのアルゴリズムセクションでは、関数が[[[construct]]]]および[[call]]]プロパティと表示されたプロトタイプのプロトタイプを含むネイティブオブジェクトであることがわかります - 将来のオブジェクトのプロトタイプ(注:ネイティブオブジェクトは、Pseudo -codeで使用されるネイティブオブジェクトの慣習です)。
コードコピーは次のとおりです。
f = new NativeObject();
f。[[class]] = "function"
.... //その他の属性
f。[[call]] = <関数への参照> //関数自体
f。[[construct]] = internalConstructor //通常の内部コンストラクター
.... //その他の属性
// fコンストラクターによって作成されたオブジェクトプロトタイプ
__ObjectPrototype = {};
__ObjectPrototype.constructor = f // {dontenum}
f.prototype = __ objectprototype
[[[call]]]]は、[[class]]属性(ここでは「機能」に相当)を除いてオブジェクトを区別する主な方法であるため、オブジェクトの内部[[call]]属性は関数として呼び出されます。そのようなオブジェクトがtypeof演算子を使用する場合、「関数」を返します。ただし、主にネイティブオブジェクトに関連しています。場合によっては、実装では、window.alert(...)の効果など、値を異なる方法で取得するためにtypeofを使用します。
コードコピーは次のとおりです。
// IEブラウザ - 「オブジェクト」、「オブジェクト」、その他のブラウザ - 「function」、「function」
alert(object.prototype.tostring.call(window.alert));
Alert(typeof window.alert); // "物体"
内部メソッド[[construct]]は、新しい演算子を備えたコンストラクターを使用してアクティブ化されます。これは、メモリの割り当てとオブジェクトの作成を担当します。パラメーターがない場合、コンストラクターを呼び出すためのブラケットも省略できます。
コードコピーは次のとおりです。
関数a(x){// constructor円
this.x = x || 10;
}
//パラメーターを渡さない場合、ブラケットも省略できます
var a = new a; //またはnew a();
アラート(ax); // 10
//パラメーターxを明示的に渡します
var b = new a(20);
アラート(BX); // 20
また、コンストラクター(初期化フェーズ)のSHIが新しく作成されたオブジェクトに設定されていることも知っています。
オブジェクト作成のアルゴリズムを研究しましょう。
オブジェクト作成アルゴリズム
内部方法[[construct]]の動作は、次のように説明できます。
コードコピーは次のとおりです。
f。[[construct]](initialParameters):
o = new NativeObject();
//プロパティ[[クラス]]は「オブジェクト」に設定されています
o。[[class]] = "object"
// f.prototypeを参照するときにオブジェクトGを取得します
var __objectprototype = f.prototype;
// __ObjectPrototypeがオブジェクトである場合、次のとおりです。
o。[[プロトタイプ]] = __ObjectPrototype
// さもないと:
o。[[プロトタイプ]] = object.prototype;
//ここにo。[[プロトタイプ]]はオブジェクトのプロトタイプです
// f。[[call]]は、新しく作成されたオブジェクトが初期化されているときに適用されます
//これを新しく作成したオブジェクトoとして設定します
//パラメーターはFの初期パラメーターと同じです
r = f。[[call]](initialParameters); this === o;
//ここにrは[[call]]の返品値です
// JSで、このように:
// r = f.Apply(o、initialParameters);
// rがオブジェクトの場合
rを返します
// さもないと
oを返します
2つの主な機能に注意してください。
1.最初に、新しく作成されたオブジェクトのプロトタイプは、現時点で関数のプロトタイププロパティから取得されます(これは、同じコンストラクターによって作成された2つの作成されたオブジェクトのプロトタイププロパティが異なる可能性があることを意味します。
2。第二に、上記のように、[[call]]がオブジェクトが初期化されたときにオブジェクトを返す場合、これはまさに新しい演算子に使用される結果です。
コードコピーは次のとおりです。
関数a(){}
a.prototype.x = 10;
var a = new a();
アラート(ax); // 10プロトタイプから入手してください
// .prototypeプロパティを新しいオブジェクトに設定します
//明示的に宣言された理由。コンストラクタ属性について以下で説明します
A.Prototype = {
コンストラクター:A、
Y:100
};
var b = new a();
//オブジェクト「B」には新しいプロパティがあります
アラート(BX); // 未定義
アラート(by); // 100プロトタイプから取得
//しかし、オブジェクトaのプロトタイプはまだ元の結果を取得できます
アラート(ax); // 10-プロトタイプから取得します
関数b(){
this.x = 10;
new array()を返します。
}
//「b」コンストラクターが返されない場合(またはこれを返します)
//このオブジェクトを使用できますが、次の状況は配列を返します
var b = new b();
アラート(BX); // 未定義
alert(object.prototype.tostring.call(b)); // [オブジェクト配列]
プロトタイプの詳細を学びましょう
プロトタイプ
各オブジェクトにはプロトタイプがあります(一部のシステムオブジェクトを除く)。プロトタイプ通信は、[[プロトタイプ]]プロトタイププロパティへの内部、暗黙的、間接アクセスを通じて実行されます。プロトタイプは、オブジェクトまたはヌル値にすることができます。
プロパティコンストラクター
上記の例には、2つの重要な知識ポイントがあります。 1つ目は、関数のコンストラクタープロパティのプロトタイププロパティに関するものです。関数作成のアルゴリズムでは、コンストラクタープロパティが関数作成段階で関数のプロトタイププロパティとして設定されていることがわかります。コンストラクタープロパティの値は、関数自体への重要な参照です。
コードコピーは次のとおりです。
関数a(){}
var a = new a();
アラート(A.Constructor); //関数a(){}、代表団
アラート(a.constructor === a); // 真実
通常、この場合、誤解があります。新しく作成されたオブジェクト自体としてプロパティを構築するのは間違っていますが、私たちが見ることができるように、このプロパティはプロトタイプに属し、継承を通じてオブジェクトにアクセスします。
コンストラクター属性のインスタンスを継承することにより、プロトタイプオブジェクトへの参照を間接的に取得できます。
コードコピーは次のとおりです。
関数a(){}
a.prototype.x = new Number(10);
var a = new a();
Alert(A.Constructor.Prototype); // [オブジェクトオブジェクト]
アラート(ax); // 10、プロトタイプによる
// a。[[プロトタイプ]]。xと同じ効果
alert(a.constructor.prototype.x); // 10
alert(a.constructor.prototype.x === ax); // 真実
ただし、機能のコンストラクターとプロトタイプの特性は、オブジェクトが作成された後に再定義できることに注意してください。この場合、オブジェクトは上記のメカニズムを失います。関数のプロトタイプ属性を介して要素のプロトタイププロトタイプを編集すると(新しいオブジェクトを追加するか、既存のオブジェクトを変更します)、インスタンスに新しく追加された属性が表示されます。
ただし、関数のプロトタイププロパティを完全に変更すると(新しいオブジェクトを割り当てることにより)、作成するオブジェクトにはコンストラクタープロパティが含まれないため、元のコンストラクターへの参照が失われます。
コードコピーは次のとおりです。
関数a(){}
A.Prototype = {
X:10
};
var a = new a();
アラート(ax); // 10
アラート(a.constructor === a); // 間違い!
したがって、関数へのプロトタイプの参照は、手動で復元する必要があります。
コードコピーは次のとおりです。
関数a(){}
A.Prototype = {
コンストラクター:A、
X:10
};
var a = new a();
アラート(ax); // 10
アラート(a.constructor === a); // 真実
コンストラクター属性は手動で復元されていますが、元の欠落しているプロトタイプと比較して、{dontenum}機能はなくなります。つまり、A.prototypeのループステートメントはもはやサポートされていませんが、[[列挙可能]]機能は、[[Enumerable]]機能を介して洞察可能な状態を制御する能力を提供することに注意してください。
コードコピーは次のとおりです。
var foo = {x:10};
object.defineProperty(foo、 "y"、{
値:20、
列挙可能:false // aka {dontenum} = true
});
console.log(foo.x、foo.y); // 10、20
for(var k in foo){
console.log(k); // "x"のみ
}
var xdesc = object.getownPropertyDescriptor(foo、 "x");
var ydesc = object.getownPropertyDescriptor(foo、 "y");
console.log(
xdesc.Nemorable、// true
ydesc.NeNumerable // false
);
明示的なプロトタイプと暗黙の[[プロトタイプ]]プロパティ
一般に、関数のプロトタイププロパティを介してオブジェクトのプロトタイプを明示的に参照することは間違っています。同じオブジェクト、オブジェクトの[[プロトタイプ]]プロパティを指します。
a。[[プロトタイプ]] --->プロトタイプ<---- A.プロトタイプ
さらに、インスタンスの[[プロトタイプ]]値は、実際にコンストラクターのプロトタイププロパティで取得されます。
ただし、プロトタイプ属性を送信することは、作成されたプロトタイプに影響しません(コンストラクターのプロトタイプ属性が変更された場合にのみ影響します)。つまり、新しく作成されたオブジェクトには新しいプロトタイプがあり、作成されたオブジェクトは元の古いプロトタイプを参照します(このプロトタイプはもはや変更できません)。
コードコピーは次のとおりです。
// A.Prototypeプロトタイプを変更する前の状況
a。[[プロトタイプ]] --->プロトタイプ<---- A.プロトタイプ
//変更後
A.プロトタイプ--->新しいプロトタイプ//新しいオブジェクトにはこのプロトタイプがあります
a。[[プロトタイプ]] --->プロトタイプ//ブーツの元のプロトタイプについて
例えば:
コードコピーは次のとおりです。
関数a(){}
a.prototype.x = 10;
var a = new a();
アラート(ax); // 10
A.Prototype = {
コンストラクター:A、
X:20
Y:30
};
//オブジェクトAは、暗黙の[[プロトタイプ]]リファレンスを介して原油のプロトタイプから得られた値です
アラート(ax); // 10
アラート(ay)//未定義
var b = new a();
//しかし、新しいオブジェクトは新しいプロトタイプから取得された値です
アラート(BX); // 20
アラート(by)// 30
したがって、一部の記事では、「プロトタイプの動的な変更は新しいプロトタイプを持つすべてのオブジェクトに影響する」と言うことは間違っており、新しいプロトタイプは、プロトタイプが変更された後に新しく作成されたオブジェクトにのみ有効になります。
ここでの主なルールは次のとおりです。オブジェクトのプロトタイプは、オブジェクトが作成されたときに作成され、その後新しいオブジェクトに変更できません。同じオブジェクトがまだ参照されている場合、コンストラクターの明示的なプロトタイプを介して参照できます。オブジェクトが作成された後、プロトタイプのプロパティは追加または変更できます。
非標準__proto__属性
ただし、いくつかの実装(Spidermonkeyなど)は、オブジェクトのプロトタイプを参照するための非標準__proto__明示的なプロパティを提供します。
コードコピーは次のとおりです。
関数a(){}
a.prototype.x = 10;
var a = new a();
アラート(ax); // 10
var __newprototype = {
コンストラクター:A、
X:20、
Y:30
};
//新しいオブジェクトを参照します
A.Prototype = __NewPrototype;
var b = new a();
アラート(BX); // 20
アラート(by); // 30
// "a"对象使用的依然是旧的原型
alert(ax); // 10
alert(ay); // 未定義
// 显式修改原型
a.__proto__ = __newPrototype;
// 现在"а"对象引用的是新对象
alert(ax); // 20
alert(ay); // 30
注意,ES5提供了Object.getPrototypeOf(O)方法,该方法直接返回对象的[[Prototype]]属性――实例的初始原型。 然而,和__proto__相比,它只是getter,它不允许set值。
コードコピーは次のとおりです。
var foo = {};
Object.getPrototypeOf(foo) == Object.prototype; // 真実
对象独立于构造函数
因为实例的原型独立于构造函数和构造函数的prototype属性,构造函数完成了自己的主要工作(创建对象)以后可以删除。原型对象通过引用[[Prototype]]属性继续存在:
コードコピーは次のとおりです。
function A() {}
A.prototype.x = 10;
var a = new A();
alert(ax); // 10
// 设置A为null - 显示引用构造函数
A = null;
// 但如果.constructor属性没有改变的话,
// 依然可以通过它创建对象
var b = new a.constructor();
alert(bx); // 10
// 隐式的引用也删除掉
delete a.constructor.prototype.constructor;
delete b.constructor.prototype.constructor;
// 通过A的构造函数再也不能创建对象了
// 但这2个对象依然有自己的原型
alert(ax); // 10
alert(bx); // 10
instanceof操作符的特性
我们是通过构造函数的prototype属性来显示引用原型的,这和instanceof操作符有关。该操作符是和原型链一起工作的,而不是构造函数,考虑到这一点,当检测对象的时候往往会有误解:
コードコピーは次のとおりです。
if (foo instanceof Foo) {
...
}
这不是用来检测对象foo是否是用Foo构造函数创建的,所有instanceof运算符只需要一个对象属性――foo.[[Prototype]],在原型链中从Foo.prototype开始检查其是否存在。instanceof运算符是通过构造函数里的内部方法[[HasInstance]]来激活的。
让我们来看看这个例子:
コードコピーは次のとおりです。
function A() {}
A.prototype.x = 10;
var a = new A();
alert(ax); // 10
alert(a instanceof A); // 真実
// 如果设置原型为null
A.prototype = null;
// ..."a"依然可以通过a.[[Prototype]]访问原型
alert(ax); // 10
// 不过,instanceof操作符不能再正常使用了
// 因为它是从构造函数的prototype属性来实现的
alert(a instanceof A); // 错误,A.prototype不是对象
另一方面,可以由构造函数来创建对象,但如果对象的[[Prototype]]属性和构造函数的prototype属性的值设置的是一样的话,instanceof检查的时候会返回true:
コードコピーは次のとおりです。
function B() {}
var b = new B();
alert(b instanceof B); // 真実
function C() {}
var __proto = {
constructor: C
};
C.prototype = __proto;
b.__proto__ = __proto;
alert(b instanceof C); // 真実
alert(b instanceof B); // 間違い
原型可以存放方法并共享属性
大部分程序里使用原型是用来存储对象的方法、默认状态和共享对象的属性。
事实上,对象可以拥有自己的状态,但方法通常是一样的。 因此,为了内存优化,方法通常是在原型里定义的。 这意味着,这个构造函数创建的所有实例都可以共享找个方法。
コードコピーは次のとおりです。
function A(x) {
this.x = x || 100;
}
A.prototype = (function () {
// 初始化上下文
// 使用额外的对象
var _someSharedVar = 500;
function _someHelper() {
alert('internal helper: ' + _someSharedVar);
}
function method1() {
alert('method1: ' + this.x);
}
function method2() {
alert('method2: ' + this.x);
_someHelper();
}
// 原型自身
戻る {
constructor: A,
method1: method1,
method2: method2
};
})();
var a = new A(10);
var b = new A(20);
a.method1(); // method1: 10
a.method2(); // method2: 10, internal helper: 500
b.method1(); // method1: 20
b.method2(); // method2: 20, internal helper: 500
// 2个对象使用的是原型里相同的方法
alert(a.method1 === b.method1); // true
alert(a.method2 === b.method2); // true
读写属性
正如我们提到,读取和写入属性值是通过内部的[[Get]]和[[Put]]方法。这些内部方法是通过属性访问器激活的:点标记法或者索引标记法:
コードコピーは次のとおりです。
// 写入
foo.bar = 10; // 调用了[[Put]]
console.log(foo.bar); // 10, 调用了[[Get]]
console.log(foo['bar']); // 效果一样
让我们用伪代码来看一下这些方法是如何工作的:
[[Get]]方法
[[Get]]也会从原型链中查询属性,所以通过对象也可以访问原型中的属性。
O.[[Get]](P):
コードコピーは次のとおりです。
// 如果是自己的属性,就返回
if (O.hasOwnProperty(P)) {
return OP;
}
// 否则,继续分析原型
var __proto = O.[[Prototype]];
// 如果原型是null,返回undefined
// 这是可能的:最顶层Object.prototype.[[Prototype]]是null
if (__proto === null) {
return undefined;
}
// 否则,对原型链递归调用[[Get]],在各层的原型中查找属性
// 直到原型为null
return __proto.[[Get]](P)
请注意,因为[[Get]]在如下情况也会返回undefined:
コードコピーは次のとおりです。
if (window.someObject) {
...
}
这里,在window里没有找到someObject属性,然后会在原型里找,原型的原型里找,以此类推,如果都找不到,按照定义就返回undefined。
注意:in操作符也可以负责查找属性(也会查找原型链):
コードコピーは次のとおりです。
if ('someObject' in window) {
...
}
这有助于避免一些特殊问题:比如即便someObject存在,在someObject等于false的时候,第一轮检测就通不过。
[[Put]]方法
[[Put]]方法可以创建、更新对象自身的属性,并且掩盖原型里的同名属性。
O.[[Put]](P, V):
コードコピーは次のとおりです。
// 如果不能给属性写值,就退出
if (!O.[[CanPut]](P)) {
戻る;
}
// 如果对象没有自身的属性,就创建它
// 所有的attributes特性都是false
if (!O.hasOwnProperty(P)) {
createNewProperty(O, P, attributes: {
ReadOnly: false,
DontEnum: false,
DontDelete: false,
Internal: false
});
}
// 如果属性存在就设置值,但不改变attributes特性
OP = V
戻る;
例えば:
コードコピーは次のとおりです。
Object.prototype.x = 100;
var foo = {};
console.log(foo.x); // 100, 继承属性
foo.x = 10; // [[Put]]
console.log(foo.x); // 10, 自身属性
delete foo.x;
console.log(foo.x); // 重新是100,继承属性
请注意,不能掩盖原型里的只读属性,赋值结果将忽略,这是由内部方法[[CanPut]]控制的。
// 例如,属性length是只读的,我们来掩盖一下length试试
function SuperString() {
/* nothing */
}
SuperString.prototype = new String("abc");
var foo = new SuperString();
console.log(foo.length); // 3, "abc"的长度
// 尝试掩盖
foo.length = 5;
console.log(foo.length); // 依然是3
但在ES5的严格模式下,如果掩盖只读属性的话,会保存TypeError错误。
属性访问器
内部方法[[Get]]和[[Put]]在ECMAScript里是通过点符号或者索引法来激活的,如果属性标示符是合法的名字的话,可以通过“.”来访问,而索引方运行动态定义名称。
コードコピーは次のとおりです。
var a = {testProperty: 10};
alert(a.testProperty); // 10, 点
alert(a['testProperty']); // 10, 索引
var propertyName = 'Property';
alert(a['test' + propertyName]); // 10, 动态属性通过索引的方式
这里有一个非常重要的特性――属性访问器总是使用ToObject规范来对待“.”左边的值。这种隐式转化和这句“在JavaScript中一切都是对象”有关系,(然而,当我们已经知道了,JavaScript里不是所有的值都是对象)。
如果对原始值进行属性访问器取值,访问之前会先对原始值进行对象包装(包括原始值),然后通过包装的对象进行访问属性,属性访问以后,包装对象就会被删除。
例えば:
コードコピーは次のとおりです。
var a = 10; // 原始值
// 但是可以访问方法(就像对象一样)
alert(a.toString()); // "10"
// 此外,我们可以在a上创建一个心属性
a.test = 100; // 好像是没问题的
// 但,[[Get]]方法没有返回该属性的值,返回的却是undefined
alert(a.test); // undefined
那么,为什么整个例子里的原始值可以访问toString方法,而不能访问新创建的test属性呢?
答案很简单:
首先,正如我们所说,使用属性访问器以后,它已经不是原始值了,而是一个包装过的中间对象(整个例子是使用new Number(a)),而toString方法这时候是通过原型链查找到的:
コードコピーは次のとおりです。
// 执行a.toString()的原理:
1. wrapper = new Number(a);
2. wrapper.toString(); // "10"
3. delete wrapper;
接下来,[[Put]]方法创建新属性时候,也是通过包装装的对象进行的:
コードコピーは次のとおりです。
// 执行a.test = 100的原理:
1. wrapper = new Number(a);
2. wrapper.test = 100;
3. delete wrapper;
我们看到,在第3步的时候,包装的对象以及删除了,随着新创建的属性页被删除了――删除包装对象本身。
然后使用[[Get]]获取test值的时候,再一次创建了包装对象,但这时候包装的对象已经没有test属性了,所以返回的是undefined:
コードコピーは次のとおりです。
// 执行a.test的原理:
1. wrapper = new Number(a);
2. wrapper.test; // undefined
这种方式解释了原始值的读取方式,另外,任何原始值如果经常用在访问属性的话,时间效率考虑,都是直接用一个对象替代它;与此相反,如果不经常访问,或者只是用于计算的话,到可以保留这种形式。
継承
我们知道,ECMAScript是使用基于原型的委托式继承。链和原型在原型链里已经提到过了。其实,所有委托的实现和原型链的查找分析都浓缩到[[Get]]方法了。
如果你完全理解[[Get]]方法,那JavaScript中的继承这个问题将不解自答了。
经常在论坛上谈论JavaScript中的继承时,我都是用一行代码来展示,事实上,我们不需要创建任何对象或函数,因为该语言已经是基于继承的了,代码如下:
コードコピーは次のとおりです。
alert(1..toString()); // "1"
我们已经知道了[[Get]]方法和属性访问器的原理了,我们来看看都发生了什么:
1.首先,从原始值1,通过new Number(1)创建包装对象
2.然后toString方法是从这个包装对象上继承得到的
为什么是继承的? 因为在ECMAScript中的对象可以有自己的属性,包装对象在这种情况下没有toString方法。 因此它是从原理里继承的,即Number.prototype。
注意有个微妙的地方,在上面的例子中的两个点不是一个错误。第一点是代表小数部分,第二个才是一个属性访问器:
コードコピーは次のとおりです。
1.toString(); // 语法错误!
(1).toString(); // OK
1..toString(); // OK
1['toString'](); // OK
原型链
让我们展示如何为用户定义对象创建原型链,非常简单:
コードコピーは次のとおりです。
function A() {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = new A();
alert([ax, ay]); // 10 (自身), 20 (继承)
function B() {}
// 最近的原型链方式就是设置对象的原型为另外一个新对象
B.prototype = new A();
// 修复原型的constructor属性,否则的话是A了
B.prototype.constructor = B;
var b = new B();
alert([bx, by]); // 10, 20, 2个都是继承的
// [[Get]] bx:
// bx (no) -->
// b.[[Prototype]].x (yes) - 10
// [[Get]] by
// by (no) -->
// b.[[Prototype]].y (no) -->
// b.[[Prototype]].[[Prototype]].y (yes) - 20
// where b.[[Prototype]] === B.prototype,
// and b.[[Prototype]].[[Prototype]] === A.prototype
这种方法有两个特性:
首先,B.prototype将包含x属性。乍一看这可能不对,你可能会想x属性是在A里定义的并且B构造函数也是这样期望的。尽管原型继承正常情况是没问题的,但B构造函数有时候可能不需要x属性,与基于class的继承相比,所有的属性都复制到后代子类里了。
尽管如此,如果有需要(模拟基于类的继承)将x属性赋给B构造函数创建的对象上,有一些方法,我们后来来展示其中一种方式。
其次,这不是一个特征而是缺点――子类原型创建的时候,构造函数的代码也执行了,我们可以看到消息"A.[[Call]] activated"显示了两次――当用A构造函数创建对象赋给B.prototype属性的时候,另外一场是a对象创建自身的时候!
下面的例子比较关键,在父类的构造函数抛出的异常:可能实际对象创建的时候需要检查吧,但很明显,同样的case,也就是就是使用这些父对象作为原型的时候就会出错。
コードコピーは次のとおりです。
function A(param) {
if (!param) {
throw 'Param required';
}
this.param = param;
}
A.prototype.x = 10;
var a = new A(20);
alert([ax, a.param]); // 10, 20
function B() {}
B.prototype = new A(); // Error
此外,在父类的构造函数有太多代码的话也是一种缺点。
解决这些“功能”和问题,程序员使用原型链的标准模式(下面展示),主要目的就是在中间包装构造函数的创建,这些包装构造函数的链里包含需要的原型。
コードコピーは次のとおりです。
function A() {
alert('A.[[Call]] activated');
this.x = 10;
}
A.prototype.y = 20;
var a = new A();
alert([ax, ay]); // 10 (自身), 20 (集成)
function B() {
// 或者使用A.apply(this, arguments)
B.superproto.constructor.apply(this, arguments);
}
// 继承:通过空的中间构造函数将原型连在一起
var F = function () {};
F.prototype = A.prototype; // 引用
B.prototype = new F();
B.superproto = A.prototype; // 显示引用到另外一个原型上, "sugar"
// 修复原型的constructor属性,否则的就是A了
B.prototype.constructor = B;
var b = new B();
alert([bx, by]); // 10 (自身), 20 (集成)
注意,我们在b实例上创建了自己的x属性,通过B.superproto.constructor调用父构造函数来引用新创建对象的上下文。
我们也修复了父构造函数在创建子原型的时候不需要的调用,此时,消息"A.[[Call]] activated"在需要的时候才会显示。
为了在原型链里重复相同的行为(中间构造函数创建,设置superproto,恢复原始构造函数),下面的模板可以封装成一个非常方面的工具函数,其目的是连接原型的时候不是根据构造函数的实际名称。
コードコピーは次のとおりです。
function inherit(child, parent) {
var F = function () {};
F.prototype = parent.prototype
child.prototype = new F();
child.prototype.constructor = child;
child.superproto = parent.prototype;
子供を返します。
}
因此,继承:
コードコピーは次のとおりです。
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A); // 连接原型
var b = new B();
alert(bx); // 10, 在A.prototype查找到
也有很多语法形式(包装而成),但所有的语法行都是为了减少上述代码里的行为。
例如,如果我们把中间的构造函数放到外面,就可以优化前面的代码(因此,只有一个函数被创建),然后重用它:
コードコピーは次のとおりです。
var inherit = (function(){
function F() {}
return function (child, parent) {
F.prototype = parent.prototype;
child.prototype = new F;
child.prototype.constructor = child;
child.superproto = parent.prototype;
子供を返します。
};
})();
由于对象的真实原型是[[Prototype]]属性,这意味着F.prototype可以很容易修改和重用,因为通过new F创建的child.prototype可以从child.prototype的当前值里获取[[Prototype]]:
コードコピーは次のとおりです。
function A() {}
A.prototype.x = 10;
function B() {}
inherit(B, A);
B.prototype.y = 20;
B.prototype.foo = function () {
alert("B#foo");
};
var b = new B();
alert(bx); // 10, 在A.prototype里查到
function C() {}
inherit(C, B);
// 使用"superproto"语法糖
// 调用父原型的同名方法
C.ptototype.foo = function () {
C.superproto.foo.call(this);
alert("C#foo");
};
var c = new C();
alert([cx, cy]); // 10, 20
c.foo(); // B#foo, C#foo
注意,ES5为原型链标准化了这个工具函数,那就是Object.create方法。ES3可以使用以下方式实现:
コードコピーは次のとおりです。
Object.create ||
Object.create = function (parent, properties) {
function F() {}
F.prototype = parent;
var child = new F;
for (var k in properties) {
child[k] = properties[k].value;
}
子供を返します。
}
// 使用法
var foo = {x: 10};
var bar = Object.create(foo, {y: {value: 20}});
console.log(bar.x, bar.y); // 10, 20
此外,所有模仿现在基于类的经典继承方式都是根据这个原则实现的,现在可以看到,它实际上不是基于类的继承,而是连接原型的一个很方便的代码重用。
結論は
本章内容已经很充分和详细了,希望这些资料对你有用,并且消除你对ECMAScript的疑问,如果你有任何问题,请留言,我们一起讨论。