Zepto 源码剖析 4 - 中心模块进口

承接第三篇末端内容,本篇连系官方 API 进入对 Zepto 中心的剖析,最先难度会比较大,须要重点明白几个中心对象的关联,方能找到线索。

$() 与 Z 对象建立

Zepto Core API 的首个要领 $() 依据其官方诠释:

Create a Zepto collection object by performing a CSS selector, wrapping DOM nodes, or creating elements from an HTML string.

经由历程运用 CSS 挑选器,或包装 DOM 节点,或从 HTML 片断中建立一个 Zepto 鸠合对象。该处即为中心模块的进口,对应 src/zepto.js 中的以下挪用:

  //\ Line 231
  $ = function(selector, context) {
    return zepto.init(selector, context);
  };
  
  //\ Line 186
  zepto.init = function(selector, context) {
    //\ 该函数的几种出口
    /*\ 1 */ return zepto.Z();
    /*\ 2 */ return $(context).find(selector);
    /*\ 3 */ return $(document).ready(selector);
    /*\ 4 */ return selector;
    /*\ 5 */ return $(context).find(selector);
    /*\ 6 */ return zepto.Z(dom, selector);
  };

$ 是一个收紧进口的 zepto.init 别号,如许的函数通报使得 zepto.init 的具值参数最多为 2 个。进入 zepto.init 函数,先疏忽中心的处置惩罚细节,注重到终究出口共有如上枚举的六种,第 1 种与第 6 种堕入一个名为 Z 的对象建立历程,下文中发明 $.fn 对象将 constructor 指向了 zepto.Z 且设定了 Z 对象的原型,此处恰是 Zepto 的组织组织体式格局地点:

  //\ Line 172
  zepto.Z = function(dom, selector) {
    return new Z(dom, selector);
  };

  //\ Line 408
  $.fn = {
    constructor: zepto.Z,
    //\ ...
  }
  
  //\ Line 938
  zepto.Z.prototype = Z.prototype = $.fn;

加载 Zepto 代码后,可在浏览器中举行以下调试即可开端熟悉 Z 对象的天生:

//\ 建立恣意一个 Zepto 对象,他的原型均指向 $.fn
> $().__proto__ === $.fn
true

//\ $ 对象中包括了 Zepto 包级别的东西函数,重要用于扩大 Zepto 功用,以 $.func 体式格局对外暴露
> Object.keys($)
(22) ["extend", "contains", "type", ... ]

//\ $.fn 对象包括了 Zepto 对外暴露的操纵 API,面向对象均为经由历程 zepto.Z 函数建立的 Z 对象
> Object.keys($.fn)
(75) ["constructor", "length", "forEach", "reduce", ... ]

//\ 因而 Z 对象上的函数挪用指向其原型 $.fn 上的同名函数
> $().hasClass === $.fn.hasClass
true

继承调试历程,剖析 Z 对象的本质:

//\ 建立一个空的 Z 对象,发明 Chrome 将其识别为一个“数组”
> $()
Z [selector: ""]

//\ 但其现实并非 Array 包装类的一个实例
> $() instanceof Array
false
> $() instanceof Zepto.zepto.Z
true

//\ Zepto 经由历程 "扩大数组" 这类体式格局使得其对外体现为一个对外包括成员变量 selector 的数组,对内能够举行函数式运算的天真数据组织
> Object.keys($())
(2) ["length", "selector"]

如许 “扩大数组” 天生的要领在于这个异常简明的函数,这里也展现了下文中 this 的指向为一个 Z 对象:

  //\ Line 128
  //\ Z 对象的 Constructor
  function Z(dom, selector) {
    var i,
      len = dom ? dom.length : 0;
    for (i = 0; i < len; i++) this[i] = dom[i];
    this.length = len;
    this.selector = selector || "";
  }

因而,回归 zepto.init 要领,其本质即为挪用该组织函数天生 Z 对象(2/3/5 出口返回的是在某个 Z 对象下操纵天生的 Z 对象,4 出口现实上传入的是 Z 对象因而直接返回本身)。Z 对象 组织函数读入的两个参数 domselector 在 Zepto 中也有明白的范例,用以保证 Z 组织函数的简朴性。

dom 的天生与 selector 的取值局限

dom 的天生严厉依赖于以下的几个正则表达式以及其天生函数 zepto.fragment,该步骤比较艰涩,简而言之功用为映照原生 dom 组织至响应 Z 对象的关联:

    //\ Line 10
    //\ 用以婚配一般的 HTML 片断,这内里 Group 1 (\w+|!) 用来拿标签范例,比方: div
    fragmentRE = /^\s*<(\w+|!)[^>]*>/,
    //\ 用以婚配单标签或两个闭合标签间没有内容的状况,如:<p> / <abbr></abbr>
    singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
    //\ 用以婚配不该自闭合的标签,Group 1 / Gruop 2 均为 Group 1 推断前提束缚的非自闭和标签
    tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,

  //\ Line 140
  zepto.fragment = function(html, name, properties) {
    var dom, nodes, container;

    //\ 假如满足 singleTagRE 就挪用 document 建立该元素,再经由历程 $() 初始化
    if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1));

    if (!dom) {
      //\ 假如声明 replace 将 html 标签修复
      if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>");
      //\ name 假如不指定,默许取标签范例
      if (name === undefined) name = fragmentRE.test(html) && RegExp.$1;
      //\ 假如 name 不在全局变量 containers 内,取 *
      if (!(name in containers)) name = "*";

      //\ 从 containers 拿详细标签,建立 html 内容
      container = containers[name];
      container.innerHTML = "" + html;
      
      //\ 将 container 中每个子节点的内容删除,此时 dom 盈余部份是一个 Z 元素
      //\ 参考:https://developer.mozilla.org/en-US/docs/Web/API/Node/removeChild
      dom = $.each(slice.call(container.childNodes), function() {
        container.removeChild(this);
      });
    }

    //\ 假如传入第三个参数为一般对象
    if (isPlainObject(properties)) {
      //\ 依据 dom 建立 Z 对象,再将 properties 中定义的属性挂载到 $(dom) 上
      nodes = $(dom);
      $.each(properties, function(key, value) {
        if (methodAttributes.indexOf(key) > -1) nodes[key](value);
        else nodes.attr(key, value);
      });
    }

    //\ 返回 dom
    return dom;
  };

剖析时期的几个疑问:

  • 为何有三个正则用于测试:

为了保证下三种挪用体式格局天生的效果雷同(现实上形貌的也是同一个空标签):

> $.zepto.fragment("<div>")
Z [div, selector: ""]
> $.zepto.fragment("<div/>")
Z [div, selector: ""]
> $.zepto.fragment("<div></div>")
Z [div, selector: ""]
  • containercontainers

containers 用于当以 name 为某个标签写为 innerHTML 时其父元素是合理的,这里仅在是表格元素的状况下替换了父容器,其他状况下默许采纳 div.

    //\ Line 22
    containers = {
      tr: document.createElement("tbody"),
      tbody: table,
      thead: table,
      tfoot: table,
      td: tableRow,
      th: tableRow,
      "*": document.createElement("div")
    },
  • isObjectisPlainObject

本函数中剖断第三个参数范例时,采纳了 isPlainObject 而不仅仅是 isObject 是为了避开数组等其他能够被识别为 Object 的状况,此处校验该对象的原型。

  //\ Line 74
  function isPlainObject(obj) {
    return (
      isObject(obj) &&
      !isWindow(obj) &&
      Object.getPrototypeOf(obj) == Object.prototype
    );
  }

selector 较 DOM 要简朴很多,假如不引入分外的 Selector 模块,其取值局限就是 Document.querySelectorAll() 这一浏览器原生要领支撑的挑选器的取值局限。(注重 Zepto 源代码中 selector 多用于标识形参,这里提到的 selector 现实上是 Z 对象中的 selector 属性)

zepto.init 的六种出口

有了上面的铺垫,终究能够进入 zepto.init,剖析其六种出口对应的 Z 对象建立或操纵历程:

  zepto.init = function(selector, context) {
    var dom
    
    //\ 出口 1: 当第一个入参为 Falsy 值,那末直接返回一个空的 Z 对象(该出口行动为建立)
    //\ 比方: $() / $(undfined)
    if (!selector) return zepto.Z()
    
    //\ 假如 selector 形参是字符串
    else if (typeof selector == 'string') {
      
      //\ 当 selector 形参第一个字符为 '<' 且相符 fragmentRE 的婚配效果,那末传入 zepto.fragment 要领,建立 Z 对象,比方:$("<p></p>")
      //\ 跳至出口 6
      if (selector[0] == '<' && fragmentRE.test(selector))
        dom = zepto.fragment(selector, RegExp.$1, context), selector = null
    
      //\ 出口 2: 假如传入的 context 不为空,那末基于 context 建立一个 Z 对象并返回包括 context 的 Z 对象(该出口行动为挑选)
      //\ 比方 $("p", { text:"Hello", id:"greeting", css:{color:'darkblue'} })
      else if (context !== undefined) return $(context).find(selector)
      
      //\ 出口3:假如没有 context 请求,则直接在全文 qsa(该出口行动为挑选)
      //\ 比方 $("p#grt")
      else dom = zepto.qsa(document, selector)
    }
    //\ 出口4:假如 selector 形参为函数,那将其挂载在 ready 要领内(该出口行动为逻辑 Defer)
    //\ 比方 $(() => alert("DONE))
    else if (isFunction(selector)) return $(document).ready(selector)
    
    //\ 出口 5: 当第一个入参已为 Z 对象时直接返回本身(该出口行动为供应兼容)
    //\ 比方 JSON.stringify($($('p'))) === JSON.stringify($('p')) 
    else if (zepto.isZ(selector)) return selector
    //\ 假如火线推断前提悉数失利,进入终究的范例推断
    else {
      //\ 假如传入的 Selector 是数组,那末去除 Falsy 值兼并,比方 $([$('div'), $('body')])
      if (isArray(selector)) dom = compact(selector)
      //\ 此处为降级前提,跳入出口 6
      else if (isObject(selector))
        dom = [selector], selector = null
      //\ 盈余部份参考出口 2/3 处置惩罚体式格局
      else if (fragmentRE.test(selector))
        dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
      else if (context !== undefined) return $(context).find(selector)
      else dom = zepto.qsa(document, selector)
    }
    
    //\ 出口 6,以上文处置惩罚范例的 dom / selector 建立 Z 对象(该出口行动为建立)
    return zepto.Z(dom, selector)
  }

此时,也能够给出 dom 对象与 Z 对象的真正关联,dom 是用于 Z 对象天生历程当中的中心 Z 对象;zepto.initzepto.fragment 必需兼并起来才真正明白 $() 能够多范例进,单范例出的设想技能及实在企图,这也是 Zepto 的英华地点。

    原文作者:cyw0ng
    原文地址: https://segmentfault.com/a/1190000017076079
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞