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[] == '<' && fragmentRE.test(selector))
dom = zepto.fragment(selector, RegExp.$, 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.$, 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行定義了幾個正則規則,我們有必要一一了解。
-
以出現任意次的空格字元開頭,<,至少出現一次的單詞字元 或者 一個!,出現任意次的(除去>)的字元,>。這個規則要比對的是:代碼段中的第一個html标簽,或者注釋。比如:fragmentRE = /^\s*<(\w+|!)[^>]*>/
或者<!DOCTYPE html>
<!-- 這是注釋 -->
-
顧名思義,這是一個單标簽比對正則。以<開頭 ,至少出現一次的字母字元,不定次的空格字元,出現0或1次的/,<,/, 反向引用符合比對規則的第一個字元串(?:比對的時候不捕獲該分組)。這個規則要比對的是:singleTagRE = /^<(\w+)\s*\/?>(?:<\/\1>|)$/
或者< img / >
這樣的标簽,無法比對<div></div>
,<img src="#"/ >
等這樣的标簽<div>lhchweb</div>
-
。比對的是根節點,忽略大小寫。以body或者html開頭,以body或者html結束的(不捕獲該分組)rootNodeRE = /^(?:body|html)$/i
-
。全局比對大寫字母。apitalRE = /([A-Z])/g
-
。以area|br|col|embed|hr|img|input|link|meta|param開頭的斷言的後顧(相對于前瞻,即排除以上這些标簽),至少出現一次的[\w:](單詞字元冒号),出現任意次的字元(排除>),/,>。全局比對,忽略大小寫。這個規則比對一個開始/結束标簽不能比對tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig
<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.$))
if (!dom) {
if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
if (name === undefined) name = fragmentRE.test(html) && RegExp.$
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) > -) 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>
.調用$.each方法對container進行處理(源碼151-156:先把container的子集轉化為數組,并周遊,然後移除它的子集)進而得到dom。container.innerHtml = ‘’ + html
- 如果傳入的properties參數是個純粹的對象(isPlainObject字面意思看),把上面得到的dom轉化為zepto對象。
。對properties對象調用$.each方法周遊(key,value)(如果傳入了一些屬性和對應的屬性值(他們以鍵值對的方式存在于properties對象中),就去查找屬性是否在methodAttributes裡,在的話得到這個屬性值。不在的話就給其設定屬性,值為對應的屬性值)。nodes = $(dom)
- 如果properties對象的屬性存在于methodAttributes(源碼17行,定義了一些特殊的屬性:
)這個數組中,那麼就得到這個屬性值(['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset']
)。比如nodes[key](value)
$('div')['css']('height')
- 反之,就給nodes節點設定屬性和屬性值(
)。比如top不在上述數組中,就給節點設定top屬性,值為對應的值。nodes.attr(key, value)
- 傳入的html參數有replace方法。就把該參數中符合tagExpanderRE正則規則的第一個第二個結果反向引用。生成标簽對。把參數html替換為這個标簽對。比如:傳入的是不完整的
藍字部分的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[] == '<' && 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()是什麼,又做了那些事情,下篇文章介紹。