『javascript設計モード』ノートの第六章:方法のチェーン呼び出し

17121 ワード

この章で実現するのはjQueryのチェーン呼び出しです.例:
$(this).setStyle('color', 'green').show();

一:呼び出しチェーンの構造
:
まず、最も簡単な$()関数の実装を見てみましょう.
function $() {
  var elements = [];
  for (var i = 0, len = arguments.length; i < len; ++i) {
    var element = arguments[i];
    if (typeof element == 'string') {
      element = document.getElementById(element);
    }
    if (arguments.length == 1) {
      return element;
    }
    elements.push(element);
  }
  return elements;
}

もし私たちが$()を実現するならば.method()という使い方は,$()関数に戻り値があり,オブジェクトを返すべきであると考えられる.前述したように、最善の方法は$()の中にnewオブジェクトを出すクラスがあるはずなので、上のコードは次のようになります.
(function() {
  // Use a private class.
  function _$(els) {
    this.elements = [];
    for (var i = 0, len = els.length; i < len; ++i) {
      var element = els[i];
      if (typeof element == 'string') {
        element = document.getElementById(element);
      }
      this.elements.push(element);
    }
  }
  // The public interface remains the same.
  window.$ = function() {
    return new _$(arguments);
  };
})();

また、オブジェクトを呼び出す方法があるはずです.prototypeに定義しましょう.コードは次のようになります.
(function() {
  function _$(els) {
    // ...
  }
  _$.prototype = {
    each: function(fn) {
      for ( var i = 0, len = this.elements.length; i < len; ++i ) {
        fn.call(this, this.elements[i]);
      }
      return this;
    },
    setStyle: function(prop, val) {
      this.each(function(el) {
        el.style[prop] = val;
      });
      return this;
    },
    show: function() {
      var that = this;
      this.each(function(el) {
        that.setStyle('display', 'block');
      });
      return this;
    },
    addEvent: function(type, fn) {
      var add = function(el) {
        if (window.addEventListener) {
          el.addEventListener(type, fn, false);
        } 
        else if (window.attachEvent) {
          el.attachEvent('on'+type, fn);
        }
      };
      this.each(function(el) {
        add(el);
      });
      return this;
    }
  };
  window.$ = function() {
    return new _$(arguments);
  };
})();

ここまで来たら、大まかに使えます.
  $('test-1', 'test-2').show().
    setStyle('color', 'red').
    addEvent('click', function(e) {
      $(this).setStyle('color', 'green');
    });

二:メソッドチェーン呼び出しをサポートするJavaScriptライブラリを設計する
:
一般的なjsライブラリには、イベント、DOM、Ajaxという重要なモジュールが含まれています.では、真似してみましょう.
Function.prototype.method = function(name, fn) {
  this.prototype[name] = fn;
  return this;
};
(function() {
  function _$(els) {
    // ...
  }
  /*    Events      * addEvent      * getEvent  */
  _$.method('addEvent', function(type, fn) {
    // ...
  }).method('getEvent', function(e) {
    // ...
  }).
  /*    DOM      * addClass      * removeClass      * replaceClass      * hasClass      * getStyle      * setStyle  */
  method('addClass', function(className) {
    // ...
  }).method('removeClass', function(className) {
    // ...
  }).method('replaceClass', function(oldClass, newClass) {
    // ...
  }).method('hasClass', function(className) {
    // ...
  }).method('getStyle', function(prop) {
    // ...
  }).method('setStyle', function(prop, val) {
    // ...
  }).
  /*    AJAX      * load. Fetches an HTML fragment from a URL and inserts it into an element.  */
  method('load', function(uri, method) {
    // ...
  });
  window.$ = function() {
    return new _$(arguments);
  });
})();

実用的なのはやはり上の方法です.
しかし、一番上の何行かのコードに気づきましたか.第1章で言ったように、prototypeを追加する方法もチェーン呼び出しで遊び始めたのを覚えています.
また、最後から4行目はwindow.$に関数を与えています.これにより、$をネーミングスペースとして使用するライブラリと競合します.例えば、jQueryと競合しています.ネーミングスペースを変更しましょう.次に、ネーミングスペースを変更するためのインストーラを実現します.
Function.prototype.method = function(name, fn) {
  // ...
};
(function() {
  function _$(els) {
    // ...
  }
  _$.method('addEvent', function(type, fn) {
    // ...
  })
  // ...
    
  window.installHelper = function(scope, interface) {
    scope[interface] = function() {
      return new _$(arguments);
    }
  };
})();

上記のコードの主な考え方は、グローバル変数に値を割り当てず、installHelperを呼び出して対応するネーミングスペースをバインドしてから使用することです.
使用方法は次のとおりです.
installHelper(window, '$');

$('example').show();

これはwindow.$に関数を割り当てることに等しい.しました.
3:コールバックを使用してチェーン呼び出しをサポートする方法からデータを取得する
:
上記のチェーンメソッドはthisを返しますが、メソッド自体が値を返しますか?では、thisに戻ると衝突するのではないでしょうか.だからこの場合、私たちはこのような状況に対して、コールバック関数を入力して、戻ってきた数値を呼び出し、その後もthisを返す方法を考えなければなりません.
例:
window.API2 = window.API2 || {};
API2.prototype = function() {
  var name = 'Hello world';
  // Privileged mutator method.
  setName: function(newName) {
    name = newName;
    return this;
  },
  // Privileged accessor method.
  getName: function(callback) {
    callback.call(this, name);
    return this;
  }
}();

上のコードはgetNameのどこを見てもいいので、コールバック関数を入力し、値を取り出して直接呼び出し、その後thisを返してチェーン呼び出しを破壊しません.
上のコードの1つの使用方法:
var o2 = new API2;
o2.getName(console.log).setName('Meow').getName(console.log);