在第一篇的时候提到过关于$()的用法,一个接口有很多重载,用法有很多种,总结了下,大概有一以下几种
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)的实现已经分析得差不多了,我一边看实现一边写笔记,不是先看完了再写的。