ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
## 回顾 上一节,我们了解了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) } ```