Zeptoソースコード分析3-qsa実現とツール関数設計


第一編の末尾の内容を受けて、この部分はzepoメインモジュールに入り、その設計思想と実現技術を分析します.
Zeptoのセレクタzepto.qsa()
  //\ Line 262
  zepto.qsa = function(element, selector) {
  };
まず最初のプロトタイプチェーン構造に直接関係しないツール関数qsaから、Zeptoの設計思想を観察する.
  //\ Line 28
  simpleSelectorRE = /^[\w-]*$/,

    //\ Line 337
    var found,
      maybeID = selector[0] == "#",
      maybeClass = !maybeID && selector[0] == ".",
      nameOnly = maybeID || maybeClass ? selector.slice(1) : selector, // Ensure that a 1 char tag name still gets checked
      isSimple = simpleSelectorRE.test(nameOnly);
関数の開始部分は、まずいくつかのBool値を定義し、idまたはclassである可能性があるかどうかを推測するために使用している.この場合、もし両者のうちの一つである可能性があるならば、マーク部分を削除する(. or #).そうでなければ、自身をnameOnlyと表記する.simpleSelectorREは、一度のマーク部分を剥離した可能性があるselectorが、一般文字列であることを満たすかどうかをテストするために使用され、そうでない場合は、複数の条件の組み合わせ(.class1.class2)である可能性があり、直接に元のquerySelectorAll方法で照会する.
   //\ Line 268
   return element.getElementById && isSimple && maybeID // Safari DocumentFragment doesn't have getElementById
      ? (found = element.getElementById(nameOnly))
        ? [found]
        : []
一連の判断を含むreturn段階に入ると、268行には互換性の注釈が現れ、前方のmaybeClass定義にはidではないと宣言されているので、ここではgetElementByIdをサポートしない方法もそのまま元に戻すquerySelectorAll方法がある.検索条件を満たしていれば、元のgetElementById`方法で照会し、配列方式の結果を返します.
  //\ Line 6
    var undefined,
    key,
    $,
    classList,
    emptyArray = [],
    concat = emptyArray.concat,
    filter = emptyArray.filter,
    slice = emptyArray.slice,

      //\ Line 270
      : element.nodeType !== 1 &&
        element.nodeType !== 9 &&
        element.nodeType !== 11
      ? []
      : slice.call(
          isSimple && !maybeID && element.getElementsByClassName // DocumentFragment doesn't have getElementsByClassName/TagName
            ? maybeClass
              ? element.getElementsByClassName(nameOnly) // If it's simple, it could be a class
              : element.getElementsByTagName(selector) // Or a tag
            : element.querySelectorAll(selector) // Or it's not simple, and we need to query all
        );
nodeTypeを参照してルート探索要素タイプを判断したが、ここではidと同じ降格ポリシーを採用し、空配列上の方法を呼び出してArray.prototype上のslice方法を呼び出して配列生成を完了し、全体Zeptoライブラリは実際に同じ考え方で原型チェーンを使ってZオブジェクト上の動作方法を与えた.
Zeptoのいくつかのツール関数の設計
Zeptoの配列は、オブジェクト関連のツール関数と比較してUnderscore.jsに似ています.いくつかの技術的な実現を重点的に列挙します.
  • タイプの関連ツール関数の例:
  •   //\ Line 29
      class2type = {},
      toString = class2type.toString,
    
      //\ Line 401
      // Populate the class2type map
      $.each(
        "Boolean Number String Function Array Date RegExp Object Error".split(" "),
        function(i, name) {
          class2type["[object " + name + "]"] = name.toLowerCase();
        }
      );
    
      //\ Line 65
      function type(obj) {
        return obj == null ? String(obj) :
          class2type[toString.call(obj)] || "object"
      }
    ツール関数type==演算子が現れ、ここでnull/undefined == nullの言語特性を利用し、Stringのパッケージクラスを通してタイプ変換して、そのタイプの文字列表現が得られ、これらの2つのタイプでない場合はclass2typeのマッピング関係によって対応する文字列タイプ名に変換される.
      //\ Line 78
      function likeArray(obj) {
        var length = !!obj && 'length' in obj && obj.length,
          type = $.type(obj)
    
        return 'function' != type && !isWindow(obj) && (
          'array' == type || length === 0 ||
            (typeof length == 'number' && length > 0 && (length - 1) in obj)
        )
      }
    ツール関数likeArrayは、実際には、lengthのNumber型メンバー変数およびKey値がlength - 1のメンバ変数が存在し、関数のオブジェクトではないZeptoが考えられる配列形態を与えている.このような定義は、ローズマリーモードを使用してもよく、初期化されていない配列項目がundefinedタイプの言語属性を適切に使用してもよい.
  • は、要素とセレクタとの整合性を判定する関数matches
  • を有する.qsa()関数と同様に、Zeptoはまた、ある要素が与えられたセレクタと一致するかどうかを判断するためのタイプマッチング関数zepto.matches()を提供する.
    //\ Line 33
    tempParent = document.createElement('div'),
    
      //\ Line 51
      zepto.matches = function(element, selector) {
      
        //\             ,        False
        if (!selector || !element || element.nodeType !== 1) return false;
        
        //\ Element.prototype.matches() -                
        //\ https://dom.spec.whatwg.org/#dom-element-matches
        var matchesSelector =
          element.matches ||
          element.webkitMatchesSelector ||
          element.mozMatchesSelector ||
          element.oMatchesSelector ||
          element.matchesSelector;
        if (matchesSelector) return matchesSelector.call(element, selector);
        
        //\            matches API,       qsa     
        //\        ,         qsa()
        //\         ,              ,         qsa()            
        // fall back to performing a selector:
        var match,
          parent = element.parentNode,
          temp = !parent;
        if (temp) (parent = tempParent).appendChild(element);
        match = ~zepto.qsa(parent, selector).indexOf(element);
        
        //\           
        temp && tempParent.removeChild(element);
        return match;
      };
    同様に、親レベルの容器を構成して、サブレベルの要素の性質の考え方を問い合わせるために、Zeptoソースコードには、例えば別のツール関数defaultDisplayの実装において複数回現れる.
  • は、現在のブラウザにある要素のデフォルトdisplay値のdefaultDisplay()関数を取得する.DOMにおける要素のデフォルトのスタイル値は、実際にユーザが変更する前に、ブラウザにノードタイプのデフォルト値を付与するので、クエリ要素のデフォルト値は、あるノードタイプのデフォルト値を照会することになる.
  • //\ Line 8
    elementDisplay = {}
    
      //\ Line 109
      function defaultDisplay(nodeName) {
        var element, display;
        //\      elementDisplay              nodeName          ,      
        if (!elementDisplay[nodeName]) {
          
          //\          ,     body              display   
          element = document.createElement(nodeName);
          document.body.appendChild(element);
          
          //\       IE      getComputedStyle()     
          display = getComputedStyle(element, "").getPropertyValue("display");
          
          //\            ,      display    none         block
          //\     none    display      $.fn.show()                     
          element.parentNode.removeChild(element);
          display == "none" && (display = "block");
          
          //\            elementDisplay
          elementDisplay[nodeName] = display;
        }
        return elementDisplay[nodeName];
      }
      
        //\ Line 574
        show: function() {
          return this.each(function() {
            this.style.display == "none" && (this.style.display = "");
            
            //\ defaultDisplay()      none      block    
            if (getComputedStyle(this, "").getPropertyValue("display") == "none")
              this.style.display = defaultDisplay(this.nodeName);
          });
         },
    Zepto荷重拡張の方法
    本節の終わりに、Zeptoを広げる方法を簡単に紹介します.メインモジュールZeptoの外に、デフォルトでコンパイルされていないモジュールSelectorは、拡張元qsa()関数の実装を含み、モジュールコードsrc/selector.jsに入る.その構造は以下の通りである.
    (function($) {
      var zepto = $.zepto,
        oldQsa = zepto.qsa,
        oldMatches = zepto.matches;
    
      zepto.qsa = function(node, selector) {
          //\     zepto.qsa   
      };
    
      zepto.matches = function(node, selector) {
          //\     zepto.matches   
      };
    })(Zepto);
    実際のコンパイルでは、Selectorをコアモジュールの後にコンパイルするだけで、元のqsa関数と対応するmatches関数を置き換えることができますので、この考え方に基づくZepto外注モジュールは非常に簡単です.コアモジュール論理を分析する際に、この方法で関数を書き換えたり、ビジネスニーズに基づいて新しいデータ構造を配置してみたり、Zeptoを利用してDOMの添削調査を実現します.