欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

zepto.js源码解读(二):zepto.init函数

程序员文章站 2022-07-13 12:47:13
...

zepto.init函数,源码也就几十行:

zepto.init = function(selector, context) {
    var dom
    // If nothing given, return an empty Zepto collection
    if (!selector) return zepto.Z()
    // Optimize for string selectors
    else if (typeof selector == 'string') {
      selector = selector.trim()
      // If it's a html fragment, create nodes from it
      // Note: In both Chrome 21 and Firefox 15, DOM error 12
      // is thrown if the fragment doesn't begin with <
      if (selector[0] == '<' && fragmentRE.test(selector))
        dom = zepto.fragment(selector, RegExp.$1, context), selector = null
      // If there's a context, create a collection on that context first, and select
      // nodes from there
      else if (context !== undefined) return $(context).find(selector)
      // If it's a CSS selector, use it to select nodes.
      else dom = zepto.qsa(document, selector)
    }
    // If a function is given, call it when the DOM is ready
    else if (isFunction(selector)) return $(document).ready(selector)
    // If a Zepto collection is given, just return it
    else if (zepto.isZ(selector)) return selector
    else {
      // normalize array if an array of nodes is given
      if (isArray(selector)) dom = compact(selector)
      // Wrap DOM nodes.
      else if (isObject(selector))
        dom = [selector], selector = null
      // If it's a html fragment, create nodes from it
      else if (fragmentRE.test(selector))
        dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
      // If there's a context, create a collection on that context first, and select
      // nodes from there
      else if (context !== undefined) return $(context).find(selector)
      // And last but no least, if it's a CSS selector, use it to select nodes.
      else dom = zepto.qsa(document, selector)
    }
    // create a new Zepto collection from the nodes found
    return zepto.Z(dom, selector)
  }

可以看到这个函数可以传入selector和context两个参数。

对照上面代码我们从init函数内部一步一步往下解读。

定义一个dom变量。

  1. 如果传的selector为空,则返回一个空的Zepto集合。
  2. 如果传入的selector是字符串类型的,对selector使用trim()方法去除字符串两边空格。
    • 如果selector的首位是<并且符合fragmentRE正则的匹配规则,即:它是代码中的第一个html标签或者注释。(dom = zepto.fragment(selector, RegExp.$1, context)),就能生成dom节点,并selector置null。
    • 如果传入了content参数,就新创建一个zepto对象($(content)),并从中find(查找)这个selector节点,并返回。
    • 如果不符合上面的情况,即传入的selector是个css选择器,dom就是在整个文档里查找到的selector节点。
  3. 如果传入的selector是个函数类型(isArray(selector))。它的返回结果是:dom树渲染完成后,执行这个函数。
  4. 如果传入的本来就是是一个Zepto集(zepto.isZ(selector)),直接返回它本身。
  5. 如果
    1. 传入的selector是数组类型(isArray(selector)),dom = compact(selector);
    2. 传入的selector是对象类型(isObject(selector)),就把节点selector放在一个数组里,赋给dom。selector置空(这样就不影响后续selector的判断).
    3. 如果selector的首位是<并且符合fragmentRE正则的匹配规则,即:它是代码中的第一个html标签或者注释。(dom = zepto.fragment(selector, RegExp.$1, context)),就能生成dom节点,selector置空。
    4. 如果传入了content参数,就新创建一个zepto对象($(content)),并从中find(查找)这个selector节点,并返回。
  6. 把最终得到的dom和selector当参数传给zepto.z()函数。
  7. zepto.init最终的返回值就是 zepto.z(dom, selector)的结果。

也就是说其实zepto.init的结果就是返回这个zepto.z()函数的值。

上面比较笼统,因为那些加红部分,有的实在不了解它到底是什么含义,所以无法很好的去理解。

源码的10-14行定义了几个正则规则,我们有必要一一了解。

  • fragmentRE = /^\s*<(\w+|!)[^>]*>/ 以出现任意次的空格字符开头,<,至少出现一次的单词字符 或者 一个!,出现任意次的(除去>)的字符,>。这个规则要匹配的是:代码段中的第一个html标签,或者注释。比如:<!DOCTYPE html>或者<!-- 这是注释 -->

  • singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/ 顾名思义,这是一个单标签匹配正则。以<开头 ,至少出现一次的字母字符,不定次的空格字符,出现0或1次的/,<,/, 反向引用符合匹配规则的第一个字符串(?:匹配的时候不捕获该分组)。这个规则要匹配的是:< img / >或者<div></div>这样的标签,无法匹配<img src="#"/ >,<div>lhchweb</div>等这样的标签

  • rootNodeRE = /^(?:body|html)$/i。匹配的是根节点,忽略大小写。以body或者html开头,以body或者html结束的(不捕获该分组)

  • apitalRE = /([A-Z])/g全局匹配大写字母

  • tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig。以area|br|col|embed|hr|img|input|link|meta|param开头的断言的后顾(相对于前瞻,即排除以上这些标签),至少出现一次的[\w:](单词字符冒号),出现任意次的字符(排除>),/,>。全局匹配,忽略大小写。这个规则匹配一个开始/结束标签不能匹配<img /> <input />等这样的自封闭标签

语言描述可能不够到位精准,建议结合代码自己分析一下。

zepto.fragment()函数也多次出现,看下代码:

// `$.zepto.fragment` takes a html string and an optional tag name
  // to generate DOM nodes from the given html string.
  // The generated DOM nodes are returned as an array.
  // This function can be overridden in plugins for example to make
  // it compatible with browsers that don't support the DOM fully.
  zepto.fragment = function(html, name, properties) {
    var dom, nodes, container

    // A special case optimization for a single tag
    if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))

    if (!dom) {
      if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
      if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
      if (!(name in containers)) name = '*'

      container = containers[name]
      container.innerHTML = '' + html
      dom = $.each(slice.call(container.childNodes), function(){
        container.removeChild(this)
      })
    }

    if (isPlainObject(properties)) {
      nodes = $(dom)
      $.each(properties, function(key, value) {
        if (methodAttributes.indexOf(key) > -1) nodes[key](value)
        else nodes.attr(key, value)
      })
    }

    return dom
  }

注释里说:zepto.fragment方法能够根据给定的参数生成DOM节点。

我们还是一步一步来看:

  1. zepto.fragment()可传入html, name, properties等作为参数。

  2. 定义dom, nodes, container

  3. 如果传入的html符合singleTagRE正则匹配规则 即它是单标签,就把它转为zepto对象,赋值给dom,if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))

  4. 如果不符合上面的正则:

    • 传入的html参数有replace方法。就把该参数中符合tagExpanderRE正则规则的第一个第二个结果反向引用。生成标签对。把参数html替换为这个标签对。比如:传入的是不完整的 <span/>那么html = <span></span> 或者传入的是<p abc/>那么html = <p abc></p>

    • 如果未传入参数name但是给定的html参数符合fragmentRE正则,即它是代码段中的第一个标签,就把这个标签名赋给name。

    • 传入了name参数,并且name参数不在给定的containers对象的属性里,就把‘*’(值为*表示会成为一个div标签)赋给name。container = containers[name](这样就能生成类似于:<div>,<tbody>这样的标签,下面有介绍)container.innerHtml = ‘’ + html.调用$.each方法对container进行处理(源码151-156:先把container的子集转化为数组,并遍历,然后移除它的子集)从而得到dom。

    • 如果传入的properties参数是个纯粹的对象(isPlainObject字面意思看),把上面得到的dom转化为zepto对象。nodes = $(dom)。对properties对象调用$.each方法遍历(key,value)(如果传入了一些属性和对应的属性值(他们以键值对的方式存在于properties对象中),就去查找属性是否在methodAttributes里,在的话得到这个属性值。不在的话就给其设置属性,值为对应的属性值)。

    • 如果properties对象的属性存在于methodAttributes(源码17行,定义了一些特殊的属性:['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'])这个数组中,那么就得到这个属性值(nodes[key](value))。比如$('div')['css']('height')

    • 反之,就给nodes节点设置属性和属性值(nodes.attr(key, value))。比如top不在上述数组中,就给节点设置top属性,值为对应的值。

蓝字部分的containers,在源码22行是这样定义的:

containers = {
      'tr': document.createElement('tbody'),
      'tbody': table, 'thead': table, 'tfoot': table,
      'td': tableRow, 'th': tableRow,
      '*': document.createElement('div')
    }

它指定了一些特定创建方法。比如tr就创建为tbody,不在containers属性里的都创建为div('*': document.createElement('div'))。

总结起来就是:

zepto.fragment方法会根据传入参数的不同,最终给我们返回dom。返回的这个dom呢,总结起来就是两种情况:

  1. 如果是单标签,dom就是一个zepto对象(dom = $(document.createElement(RegExp.$1))

  2. 如果不是,那么它就是包含这些dom节点的数组(dom = $.each(slice.call(container.childNodes),function()container.removeChild(this) }))。

而这和注释里的解释是一致的。

至于上面的isArray()方法,isObject()方法,isArray()方法,compact()方法,zepto.isZ()方法等不影响整体理解,篇幅原因不一一介绍(其实我都是从字面意思去理解他们的作用,再去查找有关的代码验证自己的猜测)。

再回头梳理一遍zepto.init方法:

它能接受两个参数(selector, context),根据传入的参数的不同分以下情况:

if (!selector) //...
else if (typeeof selector == 'string') {
    if (selector[0] == '<' && fragmentRE.test(selector)) //....
    else if (context !== undefined) //...
    else   //...
}
else if (isFunction(selector)) //...
else if (zepto.isZ(selector))  //...
else {
    if (isArray(selector))  //...
    else if (isObject(selector))  //...
    else if (context !== undefined) //...
    else  //...
}

不管分了多少种情况,总能得到dom和selector。然后把得到的dom和selector作为参数传入zepto.Z()函数,经过zepto.Z()的处理后,返回这个处理结果。

那么 zepto.Z()是什么,又做了那些事情,下篇文章介绍。

相关标签: zepto-js zepto-init