zepto学习之路--核心函数$()的实现
$()可以说是jquery的精华了,为dom操作带来了极大的灵活和方便。zepto号称“移动版的jquery”,那么它是怎么来实现这个核心函数呢?我们来详细探讨下。
1、首先,我们看下zepto中它是怎么定义的:
$ = function(selector, context) { return zepto.init(selector, context) }
这里很明显,如果你试图通过S(“”)来获取一个dom元素,zept会将其封装为一个zepto对象返回给你,那么zepto.init是如何实现的呢?
2、zepto.init
zepto.init = function(selector, context) { // If nothing given, return an empty Zepto collection if (!selector) return zepto.Z() //没有参数,返回空数组 //如果selector是个函数,则在DOM ready的时候执行它 else if (isFunction(selector)) return $(document).ready(selector) //如果selector是一个zepto.Z实例,则直接返回它自己 else if (zepto.isZ(selector)) return selector else { var dom //如果selector是一个数组,则将其里面的null,undefined去掉 if (isArray(selector)) dom = compact(selector) //如果selector是个对象,注意DOM节点的typeof值也是object,所以在里面还要再进行一次判断 else if (isObject(selector)) //如果是申明的对象,如{}, 则将selector属性copy到一个新对象,并将结果放入数组 //如果是该对象是DOM,则直接放到数组中 dom = [isPlainObject(selector) ? $.extend({}, selector) : selector], selector = null //如果selector是一段HTML代码片断,则将其转换成DOM节点 else if (fragmentRE.test(selector)) dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null //如果存在上下文context,则在上下文中查找selector,此时的selector为普通的CSS选择器 else if (context !== undefined) return $(context).find(selector) //如果没有给定上下文,则在document中查找selector,此时的selector为普通的CSS选择器 else dom = zepto.qsa(document, selector) //最后将查询结果转换成zepto集合 return zepto.Z(dom, selector) } }
在这里zepto.innit函数根据输入参数的情况来进行不同的操作,如果你输入的参数是标准的(selector,context)的形式,其会调用zepto.qsa也就是dom查询函数,最后最后将得到的元素数组用zepto.z()函数包装成zepto对象数组返回。所以这个函数的包括两个过程:一、查找生成满足条件dom元素数组;二、将dom元素包装成zepto对象,这样返回的对象就具有了zepto的方法。ok,我们继续分解这两个过程:
3、zepto.qua()
zepto.qsa = function(element, selector) { var found //当element为document,且selector为ID选择器时 return (isDocument(element) && idSelectorRE.test(selector)) ? //直接返回document.getElementById,RegExp.$1为ID的值,当没有找节点时返回[] ((found = element.getElementById(RegExp.$1)) ? [found] : []) : //当element不为元素节点或者document时,返回[] (element.nodeType !== 1 && element.nodeType !== 9) ? [] : //否则将获取到的结果转成数组并返回 slice.call( //如果selector是标签名,直接调用getElementsByClassName classSelectorRE.test(selector) ? element.getElementsByClassName(RegExp.$1) : //如果selector是标签名,直接调用getElementsByTagName tagSelectorRE.test(selector) ? element.getElementsByTagName(selector) : //否则调用querySelectorAll element.querySelectorAll(selector)) }
ok,你没看错,这就是zepto的核心dom查找函数,其思想是先用正则表达式判断selector的类型,然后调用相应的方法。对于不是单一类型的选择符,最后用querySelectorAll()函数来进行查询,并且用slice将得到的元素集合转化为数组。这里因为面向移动端,所以也没有考虑queryselectall的兼容性。在这里附带一点,在ie6,7中是不支持queryselectorall方法的,有人提出了一种补充的方法来解决兼容性:
if (!document.querySelectorAll) { document.querySelectorAll = function (selectors) { var style = document.createElement('style'), elements = [], element; document.documentElement.firstChild.appendChild(style); document._qsa = []; style.styleSheet.cssText = selectors + '{x-qsa:expression(document._qsa && document._qsa.push(this))}'; window.scrollBy(0, 0); style.parentNode.removeChild(style); while (document._qsa.length) { element = document._qsa.shift(); element.style.removeAttribute('x-qsa'); elements.push(element); } document._qsa = null; return elements; }; } if (!document.querySelector) { document.querySelector = function (selectors) { var elements = document.querySelectorAll(selectors); return (elements.length) ? elements[0] : null; }; } // 用于在IE6和IE7浏览器中,支持Element.querySelectorAll方法 var qsaWorker = (function () { var idAllocator = 10000; function qsaWorkerShim(element, selector) { var needsID = element.id === ""; if (needsID) { ++idAllocator; element.id = "__qsa" + idAllocator; } try { return document.querySelectorAll("#" + element.id + " " + selector); } finally { if (needsID) { element.id = ""; } } } function qsaWorkerWrap(element, selector) { return element.querySelectorAll(selector); } // Return the one this browser wants to use return document.createElement('div').querySelectorAll ? qsaWorkerWrap : qsaWorkerShim; })();
4、zepto.z()
在找到dom元素数组后,剩下的就是将其封装成zepto对像。
zepto.Z = function(dom, selector) { dom = dom || [] dom.__proto__ = $.fn //通过给dom设置__proto__属性指向$.fn来达到继承$.fn上所有方法的目的 dom.selector = selector || '' return dom }
没错,这个Z函数就是包装函数,短小而强悍。zepto处理的方法及其剪短,通过将$.fn赋给dom的__proto__属性,来继承fn的属性,这也就意味着这一步后,你同过$()方法得到的元素都拥有了fn中的方法。进而链式调用也就实现了。。。