## 回顧
上一節,我們了解了zepto代碼的最籠統的一個結構,而且模擬了一下代碼。其實當時還漏了一點,就是代碼里面有一個`zepto`變量不知道哪里定義的。
```javascript
var Zepto = (function(){
var $,
zepto = {}
// ...省略N行代碼...
zepto.init = function(selector, context) {
// 函數內容
}
$ = function(selector, context){
return zepto.init(selector, context)
}
// ...省略N行代碼...
return $
})()
window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)
```
即,我們在創建一個zepto對象例如執行`$('p')`時,會進入`zepto.init`這個函數,并將參數也響應傳遞。接下來就進入`zepto.init`看看這個函數。
<br>
## `zepto.init` 函數
`zepto.init`函數大約有幾十行代碼,把中間的那些`if...else...`操作去掉,剩下的就是:
```js
zepto.init = function(selector, context) {
var dom
// ...此處省略N行...
// create a new Zepto collection from the nodes found
return zepto.Z(dom, selector)
}
```
中間省略的代碼,都是根據不同條件下對`dom`變量進行賦值。`dom`從名字也可以猜測出來,它將會賦值一個或多個DOM節點。最終,它將通過`selector`一起傳遞給`zepto.Z`函數并返回值。
至于`zepto.Z`是個什么鬼暫且先不管,先看看中間省略的代碼是什么。
- **無參數,即`$()`**
```js
// If nothing given, return an empty Zepto collection
if (!selector) return zepto.Z()
```
上面代碼,如果沒有參數,將返回一個空的zepto集合,還是交給`zepto.Z`來處理,先不管。
- **`selector`參數是字符串,例如`$('p')` `$('<div>')` `$('#content')`**
```js
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)
}
```
上面代碼,如果`selector`是字符串,接下來會有三種情況。
情況1,參數為`<div>`這種形式,即是一個html標簽的,那么`dom`變量會被賦值為用這個標簽創建的DOM對象,就像`dom = document.createElement('div')`差不多。其中涉及到了`fragmentRE`和`zepto.fragment`兩個我們尚未了解的東東,此處不要深究,知道這段代碼的意思即可。
注意,通過測試發現,這里給`dom`賦值的其實不是一個dom節點對象,而是被封裝稱了數組。
```js
// 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
```
情況2,如果第二個參數有值,則先根據第二個參數生成zepto對象,然后再調用`.find`來獲取,例如`$('.item', '#content')`這種用法。`find`方法是zepto對象的一個函數,API中用法的介紹。
```js
// 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)
```
情況3,以上兩種情況都不是,則調用`zepto.qsa`來獲取數據,后來聊這個方法的具體實現。`qsa`即`querySelectAll`的縮寫,看名字能大體明白了吧?
```js
// If it's a CSS selector, use it to select nodes.
else dom = zepto.qsa(document, selector)
```
- **`selector`參數是函數,例如`$(function(){...})`**
```js
// If a function is given, call it when the DOM is ready
else if (isFunction(selector)) return $(document).ready(selector)
```
這種用法也比較常見,意思是待dom加載完畢再執行函數。這個`ready`函數的具體實現后面會講到,這里先知道意思即可。
- **`selector`本身就是個zepto對象**
這種用法比較少,但是也不能避免,例如
```js
var a = $('p');
$(a); // 這里傳入的 a 本身就是個 zepto 對象了。
```
源碼中使用`zepto.isZ`來判斷,如果是的話,直接就返回自身。`zepto.isZ`的實現很簡單,看源碼即可。
```js
// If a Zepto collection is given, just return it
else if (zepto.isZ(selector)) return selector
```
- **其他情況**
當以上情況都不符合的時候,即`selector`參數既不是空、也不是字符串、也不是函數的時候。
```js
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)
}
```
情況1,`selector`參數是數組,則通過一個`compact`處理一下賦值給`dom`。
```js
// normalize array if an array of nodes is given
if (isArray(selector)) dom = compact(selector)
```
情況2,`selector`參數是DOM節點,則將它作為數組賦值給`dom`。
```js
// Wrap DOM nodes.
else if (isObject(selector))
dom = [selector], selector = null
```
剩余情況,其實在`selector`是字符串的時候就已經考慮到了,因此感覺這里多余了。不過也可能是我考慮不周到,有疏漏的地方,如果誰發現了還望不吝賜教。
```js
// 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)
```
**這里請注意,雖然有些處理函數這一節沒有詳細看代碼實現,但是最終,賦值給`dom`的形式是一個數組。就像這段代碼**
```js
// Wrap DOM nodes.
else if (isObject(selector))
dom = [selector], selector = null
```
可以針對不同情況的`selector`,跟蹤代碼看一下。
<br>
## 總結
`zepto.init`函數算是zepto源碼中比較復雜的一個函數,一開篇就遇到了個硬骨頭。不過我們這里暫且先把那些分叉放在一邊,先把大路疏通,然后在慢慢的去一個一個攻破那些分叉。
接下來我們再把`init`函數的結構梳理一下。
```js
zepto.init = function(selector, context) {
var dom
// 分情況對dom賦值:
// 1. selector 為空
// 2. selector 是字符串,其中又分好幾種情況
// 3. selector 是函數
// 4. 其他情況,例如 selector 是數組、對象等
// create a new Zepto collection from the nodes found
return zepto.Z(dom, selector)
}
```