ホームページ >ウェブフロントエンド >jsチュートリアル >JavaScript プロトタイプの継承_JavaScript スキルについて話しましょう

JavaScript プロトタイプの継承_JavaScript スキルについて話しましょう

WBOY
WBOYオリジナル
2016-05-16 16:31:451229ブラウズ

JavaScript は本当の意味ではオブジェクト指向言語ではなく、従来の継承方法を提供しませんが、独自に提供するプロトタイプのプロパティを使用して継承を実現するプロトタイプの継承方法を提供します。

プロトタイプとプロトタイプ チェーン

プロトタイプの継承について話す前に、プロトタイプとプロトタイプ チェーンについて話す必要があります。結局のところ、これはプロトタイプの継承を実現するための基礎です。
Javascript では、各関数には独自のプロトタイプを指すプロトタイプ属性があり、この関数によって作成されたオブジェクトにもこのプロトタイプを指す __proto__ 属性があり、関数のプロトタイプはオブジェクトであるため、このオブジェクトには__proto__ は独自のプロトタイプを指し、Object オブジェクトのプロトタイプに到達するまで層ごとに深く進み、プロトタイプ チェーンを形成します。下の図は、JavaScript におけるプロトタイプとプロトタイプ チェーンの関係を非常によく説明しています。

各関数は Function 関数によって作成されたオブジェクトであるため、各関数には Function 関数のプロトタイプを指す __proto__ 属性もあります。ここで指摘しておく必要があるのは、実際にプロトタイプ チェーンを形成するのは、関数のプロトタイプ属性ではなく、各オブジェクトの __proto__ 属性であり、これが非常に重要であるということです。

プロトタイプの継承

基本モード

コードをコピーします コードは次のとおりです:

var Parent = function(){
This.name = '親' ;
} ;
Parent.prototype.getName = function(){
this.name を返します ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(){
This.name = '子' ;
} ;
Child.prototype = new Parent() ;

varparent = new Parent() ;
var child = new Child() ;

console.log(parent.getName()) //親
; console.log(child.getName()); //子

これは、プロトタイプの継承を実装する最も簡単な方法です。親クラスのオブジェクトをサブクラスのコンストラクターのプロトタイプに直接割り当て、サブクラスのオブジェクトが親クラスのプロトタイプの属性にアクセスできるようにします。親クラスのコンストラクター。 このメソッドのプロトタイプ継承図は次のとおりです。

このメソッドの利点は明らかであり、実装は非常に簡単であり、特別な操作を必要としません。同時に、サブクラスが親クラスと同じ初期化アクションを実行する必要がある場合の欠点も明らかです。コンストラクターの場合は、サブクラスで実行する必要があります。コンストラクターで、親クラスで操作を繰り返します。

コードをコピーします コードは次のとおりです:

var Parent = 関数(名前){
This.name = 名前 || '親' ;
} ;
Parent.prototype.getName = function(){
this.name を返します ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
This.name = 名前 || '子供' ;
} ;
Child.prototype = new Parent() ;

varparent = new Parent('myParent') ;
var child = new Child('myChild') ;

console.log(parent.getName()) //myParent
; console.log(child.getName()); //myChild

上記の場合、name 属性のみを初期化する必要がありますが、初期化作業が増え続ける場合、この方法は非常に不便です。そこで、以下のような改良方法が考えられる。

コンストラクターを借用

コードをコピーします コードは次のとおりです:

var Parent = 関数(名前){
This.name = 名前 || '親' ;
} ;
Parent.prototype.getName = function(){
this.name を返します ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
Parent.apply(this,arguments) ;
} ;
Child.prototype = new Parent() ;

varparent = new Parent('myParent') ;
var child = new Child('myChild') ;

console.log(parent.getName()) //myParent
; console.log(child.getName()); //myChild

上記のメソッドは、サブクラスのコンストラクターで apply を介して親クラスのコンストラクターを呼び出すことによって、同じ初期化作業を実行します。このように、親クラスでどれだけ初期化作業が行われても、サブクラスでも同じことを実行できます。初期化作業。しかし、上記の実装にはまだ問題があり、親クラスのコンストラクターはサブクラスのコンストラクターで 1 回、サブクラスのプロトタイプを割り当てるときに 1 回実行されます。これは非常に冗長であるため、まだ改善する必要があります。

コードをコピーします コードは次のとおりです:
var Parent = 関数(名前){
This.name = 名前 || '親' ;
} ;
Parent.prototype.getName = function(){
this.name を返します ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(name){

Parent.apply(this,arguments) ;
} ;
Child.prototype = 親.prototype ;

varparent = new Parent('myParent') ;

var child = new Child('myChild') ;

console.log(parent.getName()) //myParent

; console.log(child.getName()); //myChild

この方法では、子クラスのコンストラクターで親クラスのコンストラクターを 1 回実行するだけで済み、同時に親クラスのプロトタイプの属性を継承できます。これも本来の意図に沿っています。プロトタイプの再利用が必要なコンテンツをプロトタイプに配置します。プロトタイプでは、プロトタイプ内の再利用可能なコンテンツのみを継承します。上記メソッドのプロトタイプは次のとおりです:

一時コンストラクターパターン (聖杯パターン)

上記のコンストラクター パターンを借用した最後の改良バージョンにはまだ問題があります。これは、親クラスのプロトタイプをサブクラスのプロトタイプに直接代入するため、サブクラスのプロトタイプが変更された場合に問題が発生します。場合、この変更は親クラスのプロトタイプにも影響するため、これは間違いなく誰もが見たいと思うものではありません。この問題を解決するために、一時的なコンストラクター パターンがあります。

コードをコピーします コードは次のとおりです:
var Parent = 関数(名前){
This.name = 名前 || '親' ;
} ;
Parent.prototype.getName = function(){
this.name を返します ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(name){

Parent.apply(this,arguments) ;
} ;
var F = new Function(){};
F.prototype = 親.プロトタイプ ;
Child.prototype = new F() ;

varparent = new Parent('myParent') ;

var child = new Child('myChild') ;

console.log(parent.getName()) //myParent

; console.log(child.getName()); //myChild

このメソッドのプロトタイプ継承図は次のとおりです。

親クラスのプロトタイプとサブクラスのプロトタイプの間に一時的なコンストラクター F を追加することで、サブクラスのプロトタイプと親クラスのプロトタイプ間の接続が切断されることが容易にわかります。そのため、サブクラスのプロトタイプが変更されたときに、親クラスのプロトタイプには影響しません。

私の方法

「JavaScriptモード」で聖杯モードは終了ですが、上記のどの方法を使っても見つけにくい問題があります。 obj オブジェクト リテラル属性を「Parent」のプロトタイプ属性に追加しましたが、一度も使用されていないことがわかります。聖杯モデルに基づいて次の状況を見てみましょう:

コードをコピーします コードは次のとおりです:
var Parent = 関数(名前){
This.name = 名前 || '親' ;
} ;
Parent.prototype.getName = function(){
this.name を返します ;
} ;
Parent.prototype.obj = {a : 1} ;
var Child = function(name){

Parent.apply(this,arguments) ;
} ;
var F = new Function(){};
F.prototype = 親.プロトタイプ ;
Child.prototype = new F() ;

varparent = new Parent('myParent') ;

var child = new Child('myChild') ;

console.log(child.obj.a) //1

; console.log(parent.obj.a); //1
child.obj.a = 2;
console.log(child.obj.a); //2
console.log(parent.obj.a); //2

上記の状況では、子オブジェクト obj.a を変更すると、親クラスのプロトタイプ内の obj.a も変更され、共有プロトタイプと同じ問題が発生します。この状況は、child.obj.a にアクセスするときに、プロトタイプ チェーンに従って親クラスのプロトタイプを見つけ、次に obj 属性を見つけて、obj.a を変更するために発生します。次の状況を見てみましょう:

コードをコピーします コードは次のとおりです:

var Parent = 関数(名前){
This.name = 名前 || '親' ;
} ;
Parent.prototype.getName = function(){
this.name を返します ;
} ;
Parent.prototype.obj = {a : 1} ;

var Child = function(name){
Parent.apply(this,arguments) ;
} ;
var F = new Function(){};
F.prototype = 親.プロトタイプ ;
Child.prototype = new F() ;

varparent = new Parent('myParent') ;
var child = new Child('myChild') ;

console.log(child.obj.a) //1
; console.log(parent.obj.a); //1
child.obj.a = 2;
console.log(child.obj.a); //2
console.log(parent.obj.a); //2

ここで重要な問題があります。オブジェクトがプロトタイプ内のプロパティにアクセスする場合、そのプロトタイプ内のプロパティはオブジェクトに対して読み取り専用になります。つまり、子オブジェクトは obj オブジェクトを読み取ることができますが、変更することはできません。プロトタイプの obj オブジェクトを参照するため、子が obj を変更しても、そのオブジェクト自体に obj 属性が追加され、親クラスのプロトタイプの obj 属性が上書きされるだけです。子オブジェクトが obj.a を変更するとき、まずプロトタイプ内の obj への参照を読み取ります。このとき、child.obj と Parent.prototype.obj は同じオブジェクトを指すため、子による obj.a の変更は次のようになります。影響 Parent.prototype.obj.a の値。これは親クラスのオブジェクトに影響を与えます。 AngularJS の $scope ネストの継承メソッドは、JavaScript のプロトタイプ継承によって実装されています。
上記の説明によると、サブクラス オブジェクトでアクセスされるプロトタイプが親クラスのプロトタイプと同じオブジェクトである限り、上記の状況が発生するため、親クラスのプロトタイプをコピーして、それをサブクラスのプロトタイプに割り当てることができます。したがって、サブクラスがプロトタイプのプロパティを変更する場合、親クラスのプロトタイプのコピーが変更されるだけで、親クラスのプロトタイプには影響しません。具体的な実装は次のとおりです:

コードをコピーします コードは次のとおりです:

var deepClone = 関数(ソース,ターゲット){
ソース = ソース {} ;
var toStr = Object.prototype.toString ,
arrStr = '[オブジェクト配列]' ;
for(ソース内の変数 i){
If(source.hasOwnProperty(i)){
var item = ソース[i] ;
If(項目の種類 === 'オブジェクト'){
target[i] = (toStr.apply(item).toLowerCase() === arrStr) : [] {} ;
deepClone(item,target[i]); }その他{
deepClone(item,target[i]) ;
}
}
}
ターゲットを返します ;
} ;
var Parent = 関数(名前){
This.name = 名前 || '親' ;
} ;
Parent.prototype.getName = function(){
this.name を返します ;
} ;
Parent.prototype.obj = {a : '1'} ;
var Child = function(name){

Parent.apply(this,arguments) ;
} ;
Child.prototype = deepClone(Parent.prototype) ;

var child = new Child('child') ;

varparent = new Parent('parent') ;

console.log(child.obj.a) //1

; console.log(parent.obj.a); //1
child.obj.a = '2' ;
console.log(child.obj.a); //2
console.log(parent.obj.a); //1

上記のすべての考慮事項に基づいて、JavaScript 継承の具体的な実装は次のようになります。ここでは、Child と Parent が両方とも関数である場合のみを考慮します。

コードをコピー コードは次のとおりです:

var deepClone = 関数(ソース,ターゲット){
    ソース = ソース || {} ;
    var toStr = Object.prototype.toString ,
        arrStr = '[オブジェクト配列]' ;
    for(ソース内の変数 i){
        if(source.hasOwnProperty(i)){
            var item = ソース[i] ;
            if(アイテムの種類 === 'オブジェクト'){
                target[i] = (toStr.apply(item).toLowerCase() === arrStr) : [] ? {} ;
                deepClone(アイテム,ターゲット[i]) ;   
            }その他{
                deepClone(アイテム,ターゲット[i]) ;
            }
        }
    }
    ターゲットを返す ;
} ;

var extend = function(Parent,Child){
    子供 = 子供 || function(){} ;
    if(親 === 未定義)
        子を返します ;
    //借用父类构造関数数
    子 = function(){
        Parent.apply(this,argument) ;
    } ;
    // 通过深コピー贝继承父类原型
    Child.prototype = deepClone(Parent.prototype) ;
    // 重置コンストラクターのプロパティ
    Child.prototype.constructor = 子 ;
} ;

总结

これについては、Javascript での実装は非常に活発であり、最適な方法というものはなく、さまざまな要求に応じてさまざまな方法で実装する必要があり、最も重要なのは Javascript での実装を理解する必要があることです。

の原理、つまり、原型と原型チェーンの問題点を理解するだけで、独自に実装することができます。
声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。