工場の実現を継承する-JSプロトタイプチェーンの継承原理を深く理解する


機能の実現はリーフのソースコードを参考にしました.
機能紹介
私たちはクラスを作って、次の機能を実現します.
  • ベースの継承機能は、初期化関数を提供します.
  • 初期関数フック機能.
  • 内蔵オプションの継承と統合.
  • 静的属性方法.
  • mixinsです
  • 基礎継承
    JavaScriptの継承
    JavaScriptは典型的なOOP言語ではないので、継承の実現はやや煩雑で、プロトタイプチェーンの実現に基づいていますが、幸いES 6はClassのシンタックスキャンディーを実現しました.
    Leafletはブラウザの互換性のためにES 6の文法を採用していないかもしれません.また、「polyfill」という書き方も多く使われています.polyfillについては、後で専門的に紹介します.
    継承実現
    leafletには、このように書いてもいいです.
    let Parent = Class.extend({
      initialize(name) { //    
        this.name = name;
      },
      greet() {
        console.log('hello ' + this.name);
      }
    });
    
    let parent = new Parent('whj');
    parent.greet(); // hello whj
    L.C.lass.extedを使用して、オブジェクトパラメータを受信して、Partentの構造関数を作成し、その後、greet関数を呼び出してhello whjを出力します.
    実際にL.C.lass.extedは関数でクラスを実現する機能です.
    以下は実現コードです.
    function Class() {} //       Class
    
    Class.extend = function (props) { //     extend
      var NewClass = function () {
        if (this.initialize) {
          this.initialize.apply(this, arguments);//      initialize
        }                       //      ,    apply
      } 
    
      if (props.initialize){
        NewClass.prototype.initialize = props.initialize;
      }
    
      if (props.greet) {
        NewClass.prototype.greet  = props.greet;
      }
    
     return NewClass;
    };
    Classの静的方法が見られます.NewClass関数を宣言した後、パラメータにinitializeとgreetがあるかどうかを判断し、NewClassのprototypeにコピーして、最後に戻ります.戻るオブジェクトをnew操作するとinitialize関数が呼び出されます.これは最初のコードが示す機能を実現しました.
    しかし、ここに入るパラメータはinitializeまたはgreetだけがオリジナルにコピーできると限られていますが、私が入ってきたパラメータはこの二つだけではないですか?コードを修正して汎用化し、継承機能を実現します.
    Class.extend = function (props) {
      var NewClass = function () {
        if (this.initialize) {
          this.initialize.apply(this, arguments);
        }
      }
    
     //    prototype      NewClass __super__      
      var parentProto = NewClass.__super__ = this.prototype;
      var proto = Object.create(parentProto); //  parentProto proto 
                           //protos     prototype  
      proto.constructor = NewClass; 
      NewClass.prototype = proto; //      
    
      extend(proto, props); //      NewClass prototypez 
    
      return NewClass;
    };
    父タイプのプロトタイプを取り出し、Object.create関数は新たな父タイププロトタイプオブジェクトprototypeに戻り、その構造関数を現在のNewClassに向け、最後にNewClassの原型を与え、これで継承作業を完了しました.なお、NewClassはクラスを継承しただけです.継承操作が完了したらexted関数を呼び出してNewClassのプロトタイプprotoにpropsパラメータをコピーします.
    exted関数は以下の通り実現される.
    function extend(dest) {
      var i, j, len, src;
    
      for (j = 1, len = arguments.length; j < len; j++) {
        src = arguments[j];
        for (i in src) {
          dest[i] = src[i];
        }
      }
      return dest;
    }
    注意すべきは、アーグメンントの使用法であり、これは内蔵変数であり、導入されたすべてのパラメータを保存しているクラスの配列構造である.
    今は継承実現まであと一歩です.̀𒋩・́)و ̑̑ .
    function Class() { }
    
    Class.extend = function (props) {
      var NewClass = function () {
        ...
      }
        ...
     for (var i in this) {
      if (this.hasOwnProperty(i) && i !== 'prototype' && i !== '__super__') {
        NewClass[i] = this[i];
        }
      }
      ...
      return NewClass;
    };
    forループは、親タイプの静的な方法(プロトタイプにない、プロトタイプではない、スーパーではない)をNewClassにコピーします.
    現在、基本的な継承が実現されました.
    テストコード:
    let Parent = Class.extend({
      initialize(name) {
        this.name = name;
      },
      greet(word) {
        console.log(word + this.name);
      }
    });
    
    let Child = Parent.extend({
      initialize(name,age) {
        Parent.prototype.initialize.call(this,name);
        this.age = age;
      },
      greet() {
        Parent.prototype.greet.call(this,this.age);
      }
    });
    
    let child = new Child('whj',22);
    child.greet(); //22whj
    初期関数フック
    この機能は既存のクラスに新しい初期化関数を追加でき、そのサブクラスもこの関数を継承します.
    let Parent = Class.extend({
      initialize(name) {
        this.name = name;
      },
      greet(word) {
        console.log(word + this.name);
      }
    });  //       
    
    Parent.addInitHook(function () { //  init  
      console.log("Parent's other init");
    });
    
    let parent = new Parent(); // Parent's other init
    クラスの実装時に追加したinit関数が表示されます.
    この機能を完成するためにコードをさらに修正します.
    まずClassにaddInitHookという方法を追加します.
    Class.addInitHook = function (fn) {
      var init = fn;
    
      this.prototype._initHooks = this.prototype._initHooks || [];
      this.prototype._initHooks.push(init);
      return this;
    };
    関数を追加します._initHookにプッシュします._initHookの関数は後で順次呼び出されます.
    Class.extend = function (props) {
      var NewClass = function () {
        if (this.initialize) {
          this.initialize.apply(this, arguments);
        }
        this.callInitHooks(); //        init     
      }
    
      ...
    
      proto._initHooks = []; //    init    
    
      proto.callInitHooks = function () {
        ...
      };
    
      return NewClass;
    };
    まずプロトタイプに初期化関数が保存されている行列_initHook sを追加して、初期関数を追加する方法のcalInitHook sを呼び出して、最後にNewClassでcalInitHook sを呼び出します.
    今はcall InitHookの実現を見てみます.
      proto.callInitHooks = function () {
        if (this._initHooksCalled) { //           
          return;
        }
    
        if (parentProto.callInitHooks) { //          
          parentProto.callInitHooks.call(this);
        }
    
        this._initHooksCalled = true; //  init    ,     true
    
        for (var i = 0, len = proto._initHooks.length; i < len; i++) {
          proto._initHooks[i].call(this); //             
        }
      };
    この関数を実行すると、最初に再帰的に親のクラスのcalInitHook関数を呼び出します.その後、構築された_initHook配列の初期関数を循環的に呼び出します.
    内蔵オプション
    まず例のプログラムを見ます.
    var Parent= Class.extend({
        options: {
            myOption1: 'foo',
            myOption2: 'bar'
        }
    });
    
    var Child = Parent.extend({
        options: {
            myOption1: 'baz',
            myOption3: 5
        }
    });
    
    var child = new Child ();
    child.options.myOption1; // 'baz'
    child.options.myOption2; // 'bar'
    child.options.myOption3; // 5
    親およびサブクラスのいずれもオプティクスオプションを宣言しており、サブクラスはそのオプティクスを継承し、親の同名のオプティクスをカバーしています.
    以下のように実現します
    Class.extend = function (props) {
      var NewClass = function () {
        ...
      }
      ...
      if (proto.options) {
         props.options = extend(proto.options, props.options);
      }
      ...
      return NewClass;
    };
    この機能は、以前の基礎的な実現があったら、かなり簡単です.親類にoptionsのオプションがあるかどうかを判断して、子類のoptionsをコピーします.
    静的属性の方法
    var MyClass = Class.extend({
      statics: {
          FOO: 'bar',
          BLA: 5
      }
    });
    
    MyClass.FOO; // 'bar'
    以下のように実現します
    Class.extend = function (props) {
      var NewClass = function () {
        ...
      }
      ...
      if (props.statics) {
         extend(NewClass, props.statics);
         delete props.statics;
      }
      ...
    
      extend(proto, props);
    
      ...
      return NewClass;
    };
    内蔵オプションと似たようなものを実現するには、extedが実行した後、プロpsのstaticsフィールドを削除してから原型にコピーしないように注意してください.
    ミxins
    Mixinsは古いクラスに新しい属性、方法を追加する技術です.
     var MyMixin = {
        foo: function () { console.log('foo') },
        bar: 5
    };
    
    var MyClass = Class.extend({
        includes: MyMixin
    });
    
    // or 
    // MyClass.include(MyMixin);
    
    var a = new MyClass();
    a.foo(); // foo
    静的属性方法と同様の実装が可能です.
    Class.extend = function (props) {
      var NewClass = function () {
        ...
      }
      ...
      if (props.includes) {
         extend.apply(null, [proto].concat(props.includes));
         delete props.includes;
      }
      extend(proto, props); //      NewClass prototypez 
      
      return NewClass;
    };
    
    Class.include = function (props) {
       Util.extend(this.prototype, props);
       return this;
    };
    同じくexted関数を呼び出して、includeを原型にコピーしました.なぜapply方法を使うのですか?主にincludeを配列としてサポートするためです.
    締め括りをつける
    Leafletでは継承機能が全部達成されました.構想と技術を実現するために参考になります.
    これは完全な実現コードです.
    文章はWhj's Websiteで始まる.