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变量。
- 如果传的selector为空,则返回一个空的Zepto集合。
- 如果传入的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节点。
- 如果传入的selector是个函数类型(isArray(selector))。它的返回结果是:dom树渲染完成后,执行这个函数。
- 如果传入的本来就是是一个Zepto集(zepto.isZ(selector)),直接返回它本身。
- 如果
- 传入的selector是数组类型(isArray(selector)),dom = compact(selector);
- 传入的selector是对象类型(isObject(selector)),就把节点selector放在一个数组里,赋给dom。selector置空(这样就不影响后续selector的判断).
- 如果selector的首位是<并且符合fragmentRE正则的匹配规则,即:它是代码中的第一个html标签或者注释。(dom = zepto.fragment(selector, RegExp.$1, context)),就能生成dom节点,selector置空。
- 如果传入了content参数,就新创建一个zepto对象($(content)),并从中find(查找)这个selector节点,并返回。
- 把最终得到的dom和selector当参数传给zepto.z()函数。
- 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节点。
我们还是一步一步来看:
zepto.fragment()可传入html, name, properties等作为参数。
定义dom, nodes, container
如果传入的html符合singleTagRE正则匹配规则 即它是单标签,就把它转为zepto对象,赋值给dom,
if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))
。-
如果不符合上面的正则:
传入的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呢,总结起来就是两种情况:
如果是单标签,dom就是一个zepto对象(
dom = $(document.createElement(RegExp.$1))
)如果不是,那么它就是包含这些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()是什么,又做了那些事情,下篇文章介绍。