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

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()是什么,又做了那些事情,下篇文章介绍。

猜你喜欢

转载自blog.csdn.net/lihchweb/article/details/78105364