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中的方法。进而链式调用也就实现了。。。

posted @ 2015-04-21 09:53  低调的大白兔  阅读(628)  评论(0编辑  收藏  举报