• 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 

  • 相关阅读:
    c#读取XML
    Javascript 自动计算生日
    Thread.currentThread()与setDaeMon(boolean c)方法
    StringBuffer类的delete()方法和deleteCharAt()方法
    getStackTrace()方法使用
    JDBC详解
    eclipse与idea路径不匹配
    Idea导入maven项目不自动识别pom.xml
    IDEA找不到或无法加载主类
    MySQL:主从复制与处从复制同步延迟
  • 原文地址:https://www.cnblogs.com/Bond/p/4201787.html
Copyright © 2020-2023  润新知