まず、参考までに JS プロトタイプ継承の例を紹介します。具体的な内容は次のとおりです。
1. JS プロトタイプの継承
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>JS原型继承</title> </head> <body> <!--原型继承--> <script type="text/javascript"> //clone()函数用来创建新的类Person对象 var clone = function(obj) { var _f = function() {}; //这句是原型式继承最核心的地方,函数的原型对象为对象字面量 _f.prototype = obj; return new _f; } //先声明一个对象字面量 var Animal = { somthing: 'apple', eat: function() { console.log("eat " + this.somthing); } } //不需要定义一个Person的子类,只要执行一次克隆即可 var Cat = clone(Animal); //可以直接获得Person提供的默认值,也可以添加或者修改属性和方法 console.log(Cat.eat()); Cat.somthing = 'orange'; console.log(Cat.eat()); //声明子类,执行一次克隆即可 var Someone = clone(Cat); </script> </body> </html>
2. JavaScript プロトタイプ継承の仕組み
JavaScript がプロトタイプ継承を使用することはよく知られていますが、デフォルトで提供される実装インスタンスは new 演算子のみであるため、次にプロトタイプ継承とその使用方法について説明する必要があります。それをJavaScriptで記述します。
プロトタイプ継承の定義
JS プロトタイプの継承に関する説明を読むと、次のようなテキストがよく表示されます。
オブジェクトのプロパティを探すとき、JavaScript は指定された名前のプロパティが見つかるまでプロトタイプ チェーンをたどります。 ——JavaScript Secret Gardenより
JavaScript のほとんどの実装では、__proto__ 属性を使用してオブジェクトのプロトタイプ チェーンを表します。この記事では、__proto__ とプロトタイプの違いについて説明します。
注: __proto__ は非公式の使用法であり、コード内に使用すべきではありません。ここでは、JavaScript プロトタイプの継承がどのように機能するかを説明するためにのみ使用されます。
次のコードは、JS エンジンが属性を検索する方法を示しています。
function getProperty(obj, prop) { if (obj.hasOwnProperty(prop)) return obj[prop] else if (obj.__proto__ !== null) return getProperty(obj.__proto__, prop) else return undefined }
一般的な例を考えてみましょう。2 次元座標 x y を持つ 2 次元の点にも print メソッドがあります。
前に説明したプロトタイプ継承の定義を使用して、x、y、print の 3 つのプロパティを持つオブジェクト Point を作成します。新しい 2 次元の点を作成するには、新しいオブジェクトを作成し、その __proto__ 属性が Point を指すようにする必要があります:
var Point = { x: 0, y: 0, print: function () { console.log(this.x, this.y); } }; var p = {x: 10, y: 20, __proto__: Point}; p.print(); // 10 20
JavaScript の奇妙なプロトタイプ継承
混乱を招くのは、プロトタイプの継承を教える人は皆、そのようなコードを与えるのではなく、次のコードを与えるということです:
function Point(x, y) { this.x = x; this.y = y; } Point.prototype = { print: function () { console.log(this.x, this.y); } }; var p = new Point(10, 20); p.print(); // 10 20
これは約束されたものとは異なります。ここでは Point が関数になり、プロトタイプのプロパティがあり、新しい演算子が存在します。この男に何が起こっているのですか?
新しい演算子の仕組み
作成者 Brendan Eich は、JS が Java や C++ などの従来のオブジェクト指向プログラミング言語に似ていることを望んでいました。これらの言語では、 new 演算子を使用してクラスの新しいオブジェクトをインスタンス化します。そこで彼は JS で新しい演算子を書きました。
C++ にはインスタンス プロパティの初期化に使用されるコンストラクターの概念があるため、new 演算子は関数をターゲットにする必要があります。
オブジェクトのメソッドを 1 か所に配置する必要があります。プロトタイプ言語を使用しているため、それを関数のプロトタイプ属性に配置します。
new 演算子は、関数 F とその引数 (new F(arguments...)) を受け入れます。このプロセスは 3 つのステップに分かれています:
クラスのインスタンスを作成します。この手順では、空のオブジェクトの __proto__ 属性を F.prototype に設定します。
インスタンスを初期化します。関数 F は引数が渡され、キーワード this がインスタンスに設定されて呼び出されます。
インスタンスを返します。
新しい機能がどのように機能するかがわかったので、JS コードを使用して実装できます:
function New (f) { var n = { '__proto__': f.prototype }; /*第一步*/ return function () { f.apply(n, arguments); /*第二步*/ return n; /*第三步*/ }; }
彼の仕事のステータスを確認するための小さな例:
function Point(x, y) { this.x = x; this.y = y; } Point.prototype = { print: function () { console.log(this.x, this.y); } }; var p1 = new Point(10, 20); p1.print(); // 10 20 console.log(p1 instanceof Point); // true var p2 = New (Point)(10, 20); p2.print(); // 10 20 console.log(p2 instanceof Point); // true
JavaScript における真のプロトタイプの継承
JS の ECMA 仕様では、プロトタイプの継承に new 演算子の使用のみが許可されています。しかし、偉大な巨匠、ダグラス クロックフォードは、new を使用して真のプロトタイプの継承を実現する方法を発見しました。彼は Object.create 関数を次のように書きました:
Object.create = function (parent) { function F() {} F.prototype = parent; return new F(); };
これは奇妙に見えますが、非常に巧妙です。新しいオブジェクトを作成し、そのプロトタイプを任意の値に設定します。 __proto__ の使用を許可する場合は、次のように書くこともできます。
Object.create = function (parent) { return { '__proto__': parent }; };
次のコードにより、Point が真のプロトタイプの継承を採用できるようになります:
var Point = { x: 0, y: 0, print: function () { console.log(this.x, this.y); } }; var p = Object.create(Point); p.x = 10; p.y = 20; p.print(); // 10 20
結論
私たちは、JS プロトタイプ継承とは何か、そして JS がそれを特定の方法で実装する方法を学びました。ただし、真のプロトタイプ継承 (Object.create や __proto__ など) の使用には、依然として次のような欠点があります:
不十分な標準化: __proto__ は標準的な使用法ではなく、非推奨の使用法ですらありません。同時に、オリジナルの Object.create と Dao Ye が作成したオリジナル バージョンも異なります。
不十分な最適化: Object.create がネイティブであっても、カスタマイズされたものであっても、そのパフォーマンスは新規のものよりもはるかに最適化されておらず、前者は後者より最大 10 倍遅くなります。
以上がこの記事の全内容です。皆様の学習のお役に立てれば幸いです。