资讯专栏INFORMATION COLUMN

Zepto 源码分析 3 - qsa 实现与工具函数设计

ctriptech / 760人阅读

摘要:承接第一篇末尾内容,本部分开始进入主模块,分析其设计思路与实现技巧下文代码均进行过重格式化,但代码版本同第一部分内容且入口函数不变的选择器先从第一个与原型链构造不直接相关的工具函数说起,观察的设计思路。

承接第一篇末尾内容,本部分开始进入 zepto 主模块,分析其设计思路与实现技巧(下文代码均进行过重格式化,但代码 Commit 版本同第一部分内容且入口函数不变):

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 值,用以猜测是否可能为 idclass,此时如果可能是两者中的一个,那么去除标记部分(. or #),否则取自身记为 nameOnlysimpleSelectorRE 用于测试可能被剥离了一次标记部分的 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 包装类进行类型转换得到其类型的字符串表示,如果并非为这两种类型,则通过 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 实际上给出了 Zepto 所认为的数组形式,即:存在正 length 的 Number 型成员变量及 Key 值为 length - 1 的成员变量且并非是函数的对象。这样定义可以使得迭代器模式可以使用,且恰好使用了未初始化的数组项为 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 的增删改查。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/108723.html

相关文章

  • Zepto源码之Selector模块

    摘要:如果伪类的参数不可以用转换,则参数为字符串,用正则将字符串前后的或去掉,再赋值给最后执行回调,将解释出来的参数传入回调函数中,将执行结果返回。重写的方法,改过的调用的是方法,在回调函数中处理大部分逻辑。 Selector 模块是对 Zepto 选择器的扩展,使得 Zepto 选择器也可以支持部分 CSS3 选择器和 eq 等 Zepto 定义的选择器。 在阅读本篇文章之前,最好先阅读《...

    Jioby 评论0 收藏0
  • Zepto 源码之集合元素查找

    摘要:方法是将集合中不符合条件的元素查找出来。判断集合中的第一个元素是否匹配指定的选择器。这个在读源码之集合操作有讲过,如果集合个数大于零,则表示满足条件。返回集合中所有元素指定的属性值。获取集合中每个元素的前一个兄弟节点。 这篇依然是跟 dom 相关的方法,侧重点是跟集合元素查找相关的方法。 读Zepto源码系列文章已经放到了github上,欢迎star: reading-zepto 源码...

    DC_er 评论0 收藏0
  • Zepto源码分析(一)核心代码分析

    摘要:源码分析一核心代码分析源码分析二奇淫技巧总结本文只分析核心的部分代码,并且在这部分代码有删减,但是不影响代码的正常运行。当长度为则不添加内容,否则逐个将逐个到当前实例新增直接返回一个新的构造函数添加初始化方法。 Zepto源码分析(一)核心代码分析Zepto源码分析(二)奇淫技巧总结 本文只分析核心的部分代码,并且在这部分代码有删减,但是不影响代码的正常运行。 目录 * 用闭包封装Z...

    BicycleWarrior 评论0 收藏0
  • Zepto 源码分析 1 - 进入 Zepto

    摘要:选择的理由是一个用于现代浏览器的与大体兼容的库。环境搭建分析环境的搭建仅需要一个常规页面和原始代码一个常规页面打开的首页即可,在开发人员工具中即可使用原始代码本篇分析的代码参照,进入该代码分支中即可。 选择 Zepto 的理由 Zepto is a minimalist JavaScript library for modern browsers with a largely jQue...

    Aklman 评论0 收藏0
  • Zepto 源码之神奇的 $

    摘要:返回值为,如果能查找到元素,则将元素以数组的形式返回,否则返回空数组排除不合法的。的第一个字符为,并且为标签。如果存在,则查找下选择器为的所有子元素。正则表达式为如果没有指定标签名,则获取标签名。包裹元素的即为所需要获取的。 经过前面三章的铺垫,这篇终于写到了戏肉。在用 zepto 时,肯定离不开这个神奇的 $ 符号,这篇文章将会看看 zepto 是如何实现 $ 的。 读Zepto源码...

    xi4oh4o 评论0 收藏0

发表评论

0条评论

ctriptech

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<