JavaScript継承の特性と最適な実践を深く理解する

9450 ワード

継承はコード再使用のモードです.JavaScriptはクラスベースのモードをシミュレートし、他のより表現力のあるモードもサポートします.しかし、シンプルさを維持するのが最善の策です.
JavaScriptは原型に基づいた言語で、つまり直接に他のオブジェクトを継承することができます.
1ダミークラス
JavaScriptのプロトタイプは直接対象を他のオブジェクトから継承するのではなく、余分な間接層を挿入します.
関数を作成すると、Functionコンストラクタによって生成された関数オブジェクトはこのようなコードを実行します.
this.prototype = {constructor : this};
新しい関数オブジェクトにプロトタイプの属性が追加されました.これはconstructor属性を含み、属性値はこの新しい関数のオブジェクトです.
コンストラクタでモードを呼び出すと、newで関数を呼び出すと、このように実行されます.
Function.method('new', function (){
    var that = Object.create(this.prototype);//                     
    var other = this.apply(that, arguments);//       ,   this     
    return (typeof other === 'object' && other) || that;//               ,          
});
コンストラクタを定義して、プロトタイプを拡張できます.
//          
var Mammal = function (name) {
    this.name = name;
};

Mammal.prototype.get_name = function () {
    return this.name;
};

Mammal.prototype.says = function () {
    return this.saying || '';
};
次に例を作成します.
var myMammal = new Mammal('Herb the mammal');
console.log(myMammal.get_name());//Herb the mammal
もう一つの疑似クラスを作成して、Mammalを継承します.
var Cat = function (name) {
    this.name = name;
    this.saying = 'meow';
};
Cat.prototype = new Mammal();
拡張モデル:
Cat.prototype.purr = function (n) {
    var i, s = '';
    for (i = 0; i < n; i += 1) {
        if (s) {
            s += '-';
        }
        s += 'r';
    }
    return s;
};
Cat.prototype.get_name = function () {
    return this.says() + ' ' + this.name + ' ' + this.says();
};
var myCat = new Cat('Henrietta');
console.log(myCat.says());//meow
console.log(myCat.purr(5));//r-r-r-r-r
console.log(myCat.get_name());//meow Henrietta meow
method法を用いてinheritys法を定義し,これらの醜い詳細を隠す.
/**
 *   Function.prototype    method   
 * @param name     
 * @param func   
 * @returns {Function}
 */
Function.prototype.method = function (name, func) {
    if (!this.prototype[name])//      ,   
        this.prototype[name] = func;
    return this;
};

Function.method('inherits', function (Parent) {
    this.prototype = new Parent();
    return this;
});
この二つの方法はいずれもthisに戻ります.そうすると、私達はカスケード式でプログラミングできます.
var Cat = function (name) {
    this.name = name;
    this.saying = 'meow';
}.inherits(Mammal).method('purr', function (n) {
        var i, s = '';
        for (i = 0; i < n; i += 1) {
            if (s) {
                s += '-';
            }
            s += 'r';
        }
        return s;
    }).method('get_name', function () {
        return this.says() + ' ' + this.name + ' ' + this.says();
    });
var myCat = new Cat('Henrietta');
console.log(myCat.says());//meow
console.log(myCat.purr(5));//r-r-r-r-r
console.log(myCat.get_name());//meow Henrietta meow
私たちは「クラス」に似た挙動をしているコンストラクタ関数を持っていますが、プライベート環境がなく、すべての属性が公開されています.また、親の種類にアクセスする方法はありません.
構造関数の呼び出し時にnewプレフィックスの追加を忘れたら、thisは新しいオブジェクトに結合されずにグローバル変数に結合されます.このように私たちは新しいオブジェクトを拡張していないだけでなく、グローバル変数環境を破壊しました.
これは深刻な言語設計ミスです.この問題が発生する確率を下げるために、すべてのコンストラクタ関数はイニシャルで大文字で命名することを約束しています.このように頭文字の大文字の形式の関数を見たら、それはコンストラクタの関数です.O(∩∩)O~
もちろん、より良い戦略はコンストラクタ関数をまったく使用しないことです.
2オブジェクト説明子
時には、コンストラクタはパラメータをたくさん受け入れる必要があります.これは面倒くさいです.ですから、コンストラクタを作成する時、簡単なオブジェクトの説明を受けたほうがいいです.
var myObject = maker({
  first: f,
  middle: m,
  last: l
});
これらのパラメータは任意の順序で並べられます.また、コンストラクタは入って来ていないパラメータにデフォルトの値を使うことができます.コードも読みやすくなりました.
3原型
原型に基づく継承とは、新しいオブジェクトに古いオブジェクトの属性を引き継ぐことができます.まず有用なオブジェクトを作成して、そのオブジェクトと似たようなオブジェクトをより多く作ることができます.
/**
 *   
 */
var myMammal = {
    name: 'Herb the mammal',
    get_name: function () {
        return this.name;
    },
    says: function () {
        return this.saying || '';
    }
};
//     
var myCat = Object.create(myMammal);
myCat.name = 'Henrietta';
myCat.saying = 'meow';
myCat.purr = function (n) {
    var i, s = '';
    for (i = 0; i < n; i += 1) {
        if (s) {
            s += '-';
        }
        s += 'r';
    }
    return s;
};
myCat.get_name = function () {
    return this.says() + ' ' + this.name + ' ' + this.says();
};
console.log(myCat.says());//meow
console.log(myCat.purr(5));//r-r-r-r-r
console.log(myCat.get_name());//meow Henrietta meow
ここではcreate方法を使って新しい例を作成します.
Object.create = function (o) {
        var F = function () {
        };
        F.prototype = o;
        return new F();
 }
4関数化
これまで見た継承パターンの問題は、プライバシーが保護されず、オブジェクトのすべての属性が見られます.いくつかの無知なプログラマがプライベートを装うパターンを使用します.すなわち、プライベートを必要とする属性に変な名前をつけてください.他のコードを使うプログラマがそれらを見られないふりをしてください.
もっといい方法があります.モジュールモードを適用します.
まず生成対象の関数を作成します.これらのステップがあります.
  • 新しいオブジェクトを作成します.これは4つの方法があります.【1】オブジェクトの字面量を作成します.【2】コンストラクタ関数を呼び出します.【3】既存のオブジェクトの新しいインスタンスを作成します.【4】いずれかのオブジェクトを返す関数を呼び出します.
  • は、プライベートインスタンス変数および方法を定義する.
  • は、これらのパラメータにアクセスする権限を持つこの新しいオブジェクトの拡張方法である.
  • はこの新しいオブジェクトを返します.
  • 関数化コンストラクタの疑似コードは以下の通りです.
    var constructor = function (spec, my){
       var that,       ;
       my = my || {};
    
                   my  
    
      that =      
    
          that      
    
      return that;
    };
    
    specオブジェクトは、新しいインスタンスを作成する必要があるすべての情報を含み、プライベート変数または他の関数に使用できます.myオブジェクトは、継承チェーンの中のコンストラクタに共有容器を提供しています.もし入って来なかったら、myオブジェクトを作成します.
    特権方法を作成する方法は、関数をプライベートメソッドと定義して、thatに割り当てることです.
    var methodical = function (){
      ...
    };
    that.methodical = methodical;
    
    このように二段階に分けて定義される利点は、プライベートなmethodicalがこの例の変更に影響されないことである.
    ここでは、このモードをmammalの例に適用します.
    var mammal = function (spec) {
        var that = {};
        that.get_name = function () {
            return spec.name;
        };
        that.says = function () {
            return spec.saying || '';
        };
        return that;
    };
    
    var myMammal = mammal({name: 'Herb'});
    console.log(myMammal.get_name());//Herb
    
    var cat = function (spec) {
        spec.saying = spec.saying || 'meow';
        var that = mammal(spec);
        that.purr = function (n) {
            var i, s = '';
            for (i = 0; i < n; i += 1) {
                if (s) {
                    s += '-';
                }
                s += 'r';
            }
            return s;
        };
        that.get_name = function () {
            return that.says() + ' ' + spec.name + ' ' + that.says();
        };
        return that;
    };
    var myCat = cat({name: 'Henrietta'});
    console.log(myCat.says());//meow
    console.log(myCat.purr(5));//r-r-r-r-r
    console.log(myCat.get_name());//meow Henrietta meow
    
    関数化モードは、親のメソッドを呼び出すこともできます.ここではsuperior法を構築し、ある方法名を呼び出す関数を返します.
    //            
    Object.method('superior', function (name) {
        var that = this,
            method = that[name];
        return function () {
            return method.apply(that, arguments);
        };
    });
    
    今coolcatを作成します.父の方法を呼び出すことができるget_を持っています.name:
    var coolcat = function (spec) {
        var that = cat(spec),
            super_get_name = that.superior('get_name');
        that.get_name = function (n) {
            return 'like ' + super_get_name() + ' baby';
        };
        return that;
    };
    var myCoolCat = coolcat({name: 'Bix'});
    console.log(myCoolCat.get_name());//like meow Bix meow baby
    
    関数化モードは非常に柔軟性があり、パッケージ、情報隠蔽、親方法にアクセスする能力をより良く実現できます.
    オブジェクトが所有している状態がプライベートである場合は、偽造防止の対象となります.このオブジェクトの属性は置換または削除できますが、このオブジェクトの状態は影響を受けません.関数化モードでオブジェクトを作成し、このオブジェクトのすべての方法がthisまたはthatを使用しない場合、オブジェクトは永続的であり、侵入されない.特権的な方法がない限り、この永続的なオブジェクトの内部状態にはアクセスできません.
    5イベントハンドリング関数
    簡単なイベントの処理特性を任意のオブジェクトに追加できる関数を作成できます.ここでは、このオブジェクトにon方法、fire方法およびプライベートイベント登録対象を追加します.
    var eventuality = function (that) {
        var registry = {};
    
        /**
         *     
         *
         *    'on'                
         * @param              ,        type   (      )   。
         */
        that.fire = function (event) {
            var array,
                func,
                handler,
                i,
                type = typeof event === 'string' ? event : event.type;
    
            //          ,        
            if (registry.hasOwnProperty(type)) {
                array = registry[type];
                for (i = 0; i < array.length; i += 1) {
                    handler = array[i];//                  
                    func = handler.method;
                    if (typeof func === 'string') {//             ,    
                        func = this[func];
                    }
    
                    //   。          ,     ,         
                    func.apply(this, handler.parameters || [event]);
                }
            }
            return this;
        };
    
        /**
         *       
         * @param type
         * @param method
         * @param parameters
         */
        that.on = function (type, method, parameters) {
            var handler = {
                method: method,
                parameters: parameters
            };
            if (registry.hasOwnProperty(type)) {//     ,      
                registry[type].push(handler);
            } else {//  
                registry[type] = [handler];
            }
            return this;
        };
        return that;
    };
    
    任意の個別のオブジェクトでeventuallyを呼び出して、イベントの処理方法を付与することができます.thatが戻る前に、コンストラクタ内で呼び出すこともできます.
    eventuality(that);
    
    JavaScriptの弱いタイプの特性はここで大きな利点です.私たちは対象を処理しなくてもいいので、関係を受け継ぐタイプです.