zoukankan      html  css  js  c++  java
  • 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 

  • 相关阅读:
    HBase 高性能加入数据
    Please do not register multiple Pages in undefined.js 小程序报错的几种解决方案
    小程序跳转时传多个参数及获取
    vue项目 调用百度地图 BMap is not defined
    vue生命周期小笔记
    解决小程序背景图片在真机上不能查看的问题
    vue项目 菜单侧边栏随着右侧内容盒子的高度实时变化
    vue项目 一行js代码搞定点击图片放大缩小
    微信小程序进行地图导航使用地图功能
    小程序报错Do not have xx handler in current page的解决方法
  • 原文地址:https://www.cnblogs.com/Bond/p/4201787.html
Copyright © 2011-2022 走看看