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

zepto源码学习-03 $()

最初的qsa实现

zepto源码学习-03 $()

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

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

这样使用必须加如selector模块,不然浏览器会报错,如下
zepto源码学习-03 $()

报错就是说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…………

zepto源码学习-03 $()

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

上一篇:梯度爆炸/消失与初始化参数


下一篇:Red Scarf abc171_E