Fork me on GitHub

zepto源码学习-03 $()

在第一篇的时候提到过关于$()的用法,一个接口有很多重载,用法有很多种,总结了下,大概有一以下几种

  1、$(selector,context?) 传入一个选择器返回一个zepto对象

  2、$(function(){}) 传入一个函数,dom ready时执行

  3、$(html,attrs?) 传入一个html字符串,构建元素,返回一个或zepto对象

  4、$(dom obj)传入dom对象返回zepto对象

$()最终调用的zepto.init方法,对以上四种情况做相应处理,该方法有6个return,内部有六中情况,虽然是六种返回的情况,但是里面具体的处理更复杂一点。

  1、return zepto.Z(),返回一个空的zepto对象:

  2、return $(context).find(selector)

  3、return $(document).ready(selector)

  4、if (zepto.isZ(selector)) return selector

  5、return $(context).find(selector)

  6、return zepto.Z(dom, selector)

先看一个demo,都是$的相关用法

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0">
    <meta content="telephone=no" name="format-detection">
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black" />
    <meta name="description" content="">
    <meta name="keywords" content="">
</head>
<body>
    <h1 id='test'>test</h1>
    <ul id='items'>
        <li>List item 1 <span class='delete'>DELETE</span></li>
        <li>List item 2 <span class='delete'>DELETE</span></li>
    </ul>
    <div id='block'></div>
    <div id='block2'></div>
    <script type="text/javascript" src="../zepto-full-1.1.6.js"></script>
    <script>
        //1 传入选择器
        var d1=$('div');  //=> 所有页面中得div元素
        var d2=$('#test'); //=> ID 为 "test" 的元素
        var d3=$('div:first');  //=> 所有页面中第一个div元素
        // 创建元素:
        var p1=$("<p>Hello</p>"); //=> 新的p元素
        // 创建带有属性的元素:
        var p2=$("<p />", { text:"Hello", id:"greeting", css:{color:'darkblue'} });
        //=> <p id=greeting style="color:darkblue">Hello</p>

        //传入原生dom对象 或者zepto对象
        var items=document.getElementById('items');
        // 传入原生dom对象
        var $items=$(items);
        //传入zepto实例对象
        var $$items=$($items);

        // 当页面ready的时候,执行回调:
        $(function($){
            alert('Ready to Zepto!')
        })
        console.log(d1);
        console.log(d2)
        console.log(d3)
        console.log(p1)
        console.log(p2)
        console.log(items)
        console.log($items)
        console.log($$items)
    </script>
</body>
</html>

 

$(选择器)元素查找

//1 传入选择器
var d1=$('div'); //=> 所有页面中得div元素
var d2=$('#test'); //=> ID 为 "test" 的元素
var d3=$('div:first'); //=> 页面中第一个div元素

 以上情况全都是没有指定context,d1、d2最终都是在这里处理dom = zepto.qsa(document, selector) ,然后最后执行  return zepto.Z(dom, selector)。zepto.Z的实现之前已经分析过了,所以只需要分析下zepto.qsa(document, selector)的实现。

zepto默认没有添加selector模块,selector模块有限提供了支持几个最常用的伪选择器,而且可以被丢弃,与现有的代码或插件的兼容执行。d3的写法必须要有selector模块的支持。 

如果我们把selector加进来的话,zepto.qsa的实现又稍有不一样,我们这里分析最原始的 zepto.qsa

补习基础,先看下nodetype

 

 最初的qsa实现 

zepto.qsa 方法相对简单,就是做一些判断,然后根据判断最后调用相应的方法,目的是提高性能。使用getElementById、getElementsByClassName、getElementsByTagName这些方法比querySelectorAll性能要好(我没测试,推测的,如果性能一样何必话力气去做相关判断)。作者为了减少if else 嵌套,大量使用三元表达式,看起来怪怪的,不过习惯了就好。

 

现在分析var d3=$('div:first'); //=> 所有页面中第一个div元素

这样使用必须加如selector模块,不然浏览器会报错,如下

报错就是说div:first 不是一个有效的选择器,因为这个需要selector模块的支持,如果加入了selector模块,在selector里面重写了qsa和matches,selector源码如下。

;
(function($) {
    var zepto = $.zepto,
        //存储以前的zepto.qsa
        oldQsa = zepto.qsa,
        //存储以前的 zepto.matches
        oldMatches = zepto.matches

    function visible(elem) {
        elem = $(elem)
        return !!(elem.width() || elem.height()) && elem.css("display") !== "none"
    }

    // Implements a subset from:
    // http://api.jquery.com/category/selectors/jquery-selector-extensions/
    //
    // Each filter function receives the current index, all nodes in the
    // considered set, and a value if there were parentheses. The value
    // of `this` is the node currently being considered. The function returns the
    // resulting node(s), null, or undefined.
    //
    // Complex selectors are not supported:
    //   li:has(label:contains("foo")) + li:has(label:contains("bar"))
    //   ul.inner:first > li
    var filters = $.expr[':'] = {
        visible: function() {
            if (visible(this)) return this
        },
        hidden: function() {
            if (!visible(this)) return this
        },
        selected: function() {
            if (this.selected) return this
        },
        checked: function() {
            if (this.checked) return this
        },
        parent: function() {
            return this.parentNode
        },
        first: function(idx) {
            if (idx === 0) return this
        },
        last: function(idx, nodes) {
            if (idx === nodes.length - 1) return this
        },
        eq: function(idx, _, value) {
            if (idx === value) return this
        },
        contains: function(idx, _, text) {
            if ($(this).text().indexOf(text) > -1) return this
        },
        has: function(idx, _, sel) {
            if (zepto.qsa(this, sel).length) return this
        }
    }

    var filterRe = new RegExp('(.*):(\\w+)(?:\\(([^)]+)\\))?$\\s*'),
        childRe = /^\s*>/,
        classTag = 'Zepto' + (+new Date())

    function process(sel, fn) {
        // quote the hash in `a[href^=#]` expression
        // 把# 加上引号
        sel = sel.replace(/=#\]/g, '="#"]')
            //$('div:first')=====>["div:first", "div", "first", undefined]
            //$('div:eq(0)')=====>["div:eq(0)", "div", "eq", "0"]
        var filter, arg, match = filterRe.exec(sel)
            //匹配到的伪类选择必须是filters中有的visible、hidden、selected、checked、parent、first、last、eq、contains、has
        if (match && match[2] in filters) {
            //取出对应的处理函数
            filter = filters[match[2]],
                //数组的地四个元素,其实就是元素索引值,eq的时候会有
                arg = match[3]
                //第一个值
            sel = match[1]
                //取得eq(num) 里面的num
            if (arg) {
                var num = Number(arg)
                if (isNaN(num)) arg = arg.replace(/^["']|["']$/g, '')
                else arg = num
            }
        }
        //调用fn 传入选择器、filter、和索引值
        return fn(sel, filter, arg)
    }

    zepto.qsa = function(node, selector) {
        //直接调用process 然后返回
        return process(selector, function(sel, filter, arg) {
            try {
                var taggedParent
                    //如果没有传入selector 又有filter 此时设置sel=*
                if (!sel && filter) sel = '*'
                else if (childRe.test(sel))
                // support "> *" child queries by tagging the parent node with a
                // unique class and prepending that classname onto the selector
                //给node添加一个class, sel=随即字符串加上之前的slector,最后再当前node下面去寻找对应的元素
                    taggedParent = $(node).addClass(classTag), sel = '.' + classTag + ' ' + sel
                //调用以前的zepto.qsa 查找对应元素,这里var,是因为js没有块级作用域
                var nodes = oldQsa(node, sel)
            } catch (e) {
                console.error('error performing selector: %o', selector)
                throw e
            } finally {
                //去掉taggedParent之前添加的class
                if (taggedParent) taggedParent.removeClass(classTag)
            }
            //是否有filter,如果有就过滤查找到的nodes节点
            /*
            *
            * 先调用$.map方法,过滤nodes
            *
            $.map([1,2,3,4,5],function(item,index){
                    if(item>1) return item*item;
            });    // =>[4, 9, 16, 25]
            //得到经过filter函数过滤后的节点集合,再次调用zepto.uniq去掉重复的元素
            zepto.uniq=return emptyArray.filter.call(array, function(item, idx) {
                return array.indexOf(item) == idx
            })
             */
            return !filter ? nodes :
                zepto.uniq($.map(nodes, function(n, i) {
                    //调用filter,传入item、index、nodes、索引值
                    return filter.call(n, i, nodes, arg)
                }))
        })
    }

    zepto.matches = function(node, selector) {
        return process(selector, function(sel, filter, arg) {
            return (!sel || oldMatches(node, sel)) &&
                (!filter || filter.call(node, null, arg) === node)
        })
    }
})(Zepto); 

 

关于选择器基本上没什么难的,selector的代码相对简单,涉及一些正则,js没有块级作用域。

说完了元素查找,接下来看看元素创建 

// 创建元素:
var p1=$("<p>Hello</p>"); //=> 新的p元素
// 创建带有属性的元素:
var p2=$("<p />", { text:"Hello", id:"greeting", css:{color:'darkblue'} });
//=> <p id=greeting style="color:darkblue">Hello</p>

查看源码,这种情况最后都是调用以下方法处理的

if (selector[0] == '<' && fragmentRE.test(selector))
                dom = zepto.fragment(selector, RegExp.$1, context), selector = null

else if (fragmentRE.test(selector))
                dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null

可见最后都是调用zepto.fragment这个方法,第一个参数传入html字符串,第二个参数为寻找到的name,第三个是上下文

//标签及html注释的正则
fragmentRE = /^\s*<(\w+|!)[^>]*>/; 传入的第二个参数RegExp.$1,RegExp.$1应该是取得最近一次匹配的标签,其实就是找到的name,比如:div、p、span…………

fragment的具体实现如下:

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.$1))
        //dom没有被赋值,不是单个标签
        if (!dom) {
            ////将类似<div class="test"/>替换成<div class="test"></div>  对标签进行修复
            if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
            //外面没有传name这里指定name
            if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
            //设置容器标签名,如果不是tr,tbody,thead,tfoot,td,th,则容器标签名为div
            if (!(name in containers)) name = '*'
            //取到对应的容器
            container = containers[name]
            //将html代码片断放入容器
            container.innerHTML = '' + html
            //取容器的子节点,这样就直接把字符串转成DOM节点了。
            //先取到容器的子节点,再转换为数组,然后在挨个从容器中移除,最后返回节点数组
            dom = $.each(slice.call(container.childNodes), function() {
                container.removeChild(this)
            })
        }
        //后面有设置相关属性、 则将其当作属性来给添加进来的节点进行设置
        if (isPlainObject(properties)) {
            nodes = $(dom)//将dom转成zepto对象,为了方便下面调用zepto上的方法
            //遍历对象,设置属性
            $.each(properties, function(key, value) {
                //如果设置的是'val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset',则调用zepto上相对应的方法
                if (methodAttributes.indexOf(key) > -1) nodes[key](value)
                else nodes.attr(key, value)
            })
        }
        return dom
    }

 

$(dom对象)、$(zepto对象)

 这两种情况的处理相对简单,不说了,之前分析zepto.init的实现有说到

$(function)

 这个就是我们经常使用的dom ready,在zepto.init中最后都是 $(document).ready(selector)。这句话的意思是先创建一个zepto实例对象,然后调用其ready方法,所以我们只需要找到$.fn.ready的实现即可。

        ready: function(callback) {
            // need to check if document.body exists for IE as that browser reports
            // document ready when it hasn't yet created the body element
            if (readyRE.test(document.readyState) && document.body) callback($)
            else document.addEventListener('DOMContentLoaded', function() {
                callback($)
            }, false)
            return this
        },

最终发现这个实现很简单,几乎没有要说的。JQuery的ready依赖了Deferred相对复杂点。

最后再说$(selector,context)指定上下文对象,zepto.init方法里面的处理都是$(context).find(selector)。所以我们只需要查看$.fn.find方法即可

find: function(selector) {
            var result, $this = this
            if (!selector) result = $()
            else if (typeof selector == 'object')
                //找到所有符合selector的元素,然后在过滤
                result = $(selector).filter(function() {
                    var node = this
                    return emptyArray.some.call($this, function(parent) {
                        //是mode的子节点
                        return $.contains(parent, node)
                    })
                })
                //this只有一个元素
            else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
                //this包含多个节点对象,挨个查找每个元素下面符合selector的元素
            else result = this.map(function() {
                return zepto.qsa(this, selector)
            })
            return result
        },

 

 到此基本上$(XXXX)的实现已经分析得差不多了,我一边看实现一边写笔记,不是先看完了再写的。 

 本文地址 :http://www.cnblogs.com/Bond/p/4201787.html 

 

posted @ 2015-01-05 11:18  xj3614  阅读(752)  评论(0编辑  收藏  举报