Fork me on GitHub

zepto.js源码分析

总体结构

1
2
3
4
5
6
var Zepto = (function() {
return $
})()
window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)

这里是一个自执行函数的返回值赋值给了Zepto,在下面给window下面的Zepto赋值,我们就可以通过Zepto(“#id”)这样来调用,之后的也是赋值给了window下的$,这也是我们最常见的$(“#id”)

初始化

我们已经知道总体是怎么回事,那么我们就看看自执行函数里面返回的$是什么

1
2
3
$ = function(selector, context){
return zepto.init(selector, context)
}

这里可以看出这个$返回了一个初始化函数,这个初始化函数主要的左右是来判断$(“”)这里面到底是什么样式,根据不同的样式来分配给不同的方法处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
zepto.init = function(selector, context) {
var dom
//如果是空的话就返回,一会再看zepto.Z()这个函数,其实就是绑定了原型链。
if (!selector) return zepto.Z()
else if (typeof selector == 'string') {
//两种情况$("<p></p>")和$("#id")
selector = selector.trim()
// fragmentRE = /^\s*<(\w+|!)[^>]*>/,下面这个就是第一种创建元素的情况
//这个正则表达式的意思是开头匹配(0,)个回车空格等符号下一个字符是<的之后有有{1,}个字母或数字,或者是!,之后有{0,}个>结尾,这个正则只能匹配出创建元素的情况,但不足以验证其内容的正确性,之后在方法里有验证其内容的正则表达式。zepto.fragment是来验证其内容的。因为很明显<2></2>也符合验证
if (selector[0] == '<' && fragmentRE.test(selector))
//下面的方法是来确认表达式,在创建元素,以后再展开吧。这里RegExp.$1为第一个子匹配(表达式中括号的部分)例如<p></p>匹配的就是p这样在fragment中p就是最外层的dom否则默认为div
dom = zepto.fragment(selector, RegExp.$1, context), selector = null
// $("#id","li")
else if (context !== undefined) return $(context).find(selector)
// $("#id")等我们常用的就在这个方法里
else dom = zepto.qsa(document, selector)
}
//最后都返回一个dom元素节点,是个数组类型的里面装有原型链方法,之后再说
如果是$(function(){})这个样式的就直接调用ready
else if (isFunction(selector)) return $(document).ready(selector)
// 传入的参数本身就已经是 zepto 对象,则直接返回
else if (zepto.isZ(selector)) return selector
else {
// compact函数:过滤掉为null的项
if (isArray(selector)) dom = compact(selector)
// 如果传入的是object,直接强制塞进一个数组
else if (isObject(selector)) dom = [selector], selector = null
// fragmentRE = /^\s*<(\w+|!)[^>]*>/ 还是取出第一个标签跟上面的差不多
else if (fragmentRE.test(selector))
dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
else if (context !== undefined) return $(context).find(selector)
else dom = zepto.qsa(document, selector)
}
return zepto.Z(dom, selector)
}

最后调用的是zepto.z(dom, selector)该方法是通过选择器表达式查找DOM

zepto.Z.prototype 继承所有$.fn所有原型方法

1
zepto.Z.prototype = Z.prototype = $.fn

在fn对象里面创建常用的方法

下面是一些用到的知识点

matchesSelector

在SELECTORS API Level 2规范中,为DOM节点添加了一个方法,主要是用来判断当前DOM节点不否能完全匹配对应的CSS选择器规则;如果匹配成功,返回true,反之则返回false。语法如下:

1
element.matches(String selector);

这个方法在我们做事件委托时就显得非常有用,并且jquery也封装了类似得方法来做委托,示例代码如下:

1
2
3
4
5
6
document.querySelector('#wrap').addEventListener('click',function(e){
if(e.target.matches('a.btn')) {
e.preventDefault();
//TODO something
}
},false);

zepto的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
zepto.matches = function(element, selector) {
//没参数,非元素,直接返回
if(!selector || !element || element.nodeType !== 1) return false
//如果浏览器支持MatchesSelector 直接调用
var matchesSelector = element.webkitMatchesSelector || element.mozMatchesSelector ||
element.oMatchesSelector || element.matchesSelector
if(matchesSelector) return matchesSelector.call(element, selector)
//浏览器不支持MatchesSelector
var match, parent = element.parentNode,
temp = !parent
//元素没有父元素,存入到临时的div tempParent
if(temp)(parent = tempParent).appendChild(element)
//再通过父元素来搜索此表达式。 找不到-1 找到有索引从0开始
//注意 ~取反位运算符 作用是将值取负数再减1 如-1变成0 0变成-1
match = ~zepto.qsa(parent, selector).indexOf(element)
//清理临时父节点
temp && tempParent.removeChild(element)
//返回匹配
return match
}

检测对象基本对象

这是一个十分常见的问题,用 typeof 是否能准确判断一个对象变量,答案是否定的,null 的结果也是 object,Array 的结果也是 object,有时候我们需要的是 “纯粹” 的 object 对象。如何避免呢?比较好的方式是:

1
console.log(Object.prototype.toString.call(obj) === "[object Object]");

如下面的使用方法

1
2
3
4
5
6
7
8
9
10
11
12
console.log(Object.prototype.toString.call("jerry"));//[object String]
console.log(Object.prototype.toString.call(12));//[object Number]
console.log(Object.prototype.toString.call(true));//[object Boolean]
console.log(Object.prototype.toString.call(undefined));//[object Undefined]
console.log(Object.prototype.toString.call(null));//[object Null]
console.log(Object.prototype.toString.call({name: "jerry"}));//[object Object]
console.log(Object.prototype.toString.call(function(){}));//[object Function]
console.log(Object.prototype.toString.call([]));//[object Array]
console.log(Object.prototype.toString.call(new Date));//[object Date]
console.log(Object.prototype.toString.call(/\d/));//[object RegExp]
function Person(){};
console.log(Object.prototype.toString.call(new Person));//[object Object]

toString为Object的原型方法,而Array ,function等类型作为Object的实例,都重写了toString方法。不同的对象类型调用toString方法时,根据原型链的知识,调用的是对应的重写之后的toString方法(function类型返回内容为函数体的字符串,Array类型返回元素组成的字符串…..),而不会去调用Object上原型toString方法(返回对象的具体类型),所以采用obj.toString()不能得到其对象类型,只能将obj转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用Object上原型toString方法。

touch.js

click事件在移动端上会有 300ms 的延迟,同时因为需要 长按,双触击 等富交互,所以我们通常都会引入类似 zepto 这样的库。zepto 实现了’swipe’, ‘swipeLeft’, ‘swipeRight’, ‘swipeUp’, ‘swipeDown’, ‘doubleTap’, ‘tap’, ‘singleTap’, ‘longTap’ 这样一些功能。

MSGesture

Windows 8 Release Preview 的 IE10 中引入了 JavaScript 中的手势识别对象。网站可以创建手势对象,决定处理哪些指针(鼠标、手写笔或触摸)并将手势事件指向相应的元素。然后,浏览器将计算正执行的手势并通过事件通知页面。这样开发人员便可构建出在其他任何本机浏览器中都尚无法实现的手势体验。其中包括多个并发手势(例如,使用您的双手旋转两块拼图)。

我们来看一下这一切是如何在代码中实现的。

在您的网站中处理手势的第一步是实例化手势对象。

1
var myGesture = new MSGesture();

接下来,为该手势提供一个目标元素。浏览器将对该元素触发手势事件。同时,该元素还可以确定事件的坐标空间。

1
2
3
elm = document.getElementById("someElement");
myGesture.target = elm;
elm.addEventListener("MSGestureChange", handleGesture);

最后,告知手势对象在手势识别期间处理哪些指针。

1
2
3
4
elm.addEventListener("MSPointerDown", function (evt) {
// adds the current mouse, pen, or touch contact for gesture recognition
myGesture.addPointer(evt.pointerId);
});
1
2
3
4
5
6
7
$(document).ready
判断是否支持MSGesture事件
if('MSGesture' in window) {
gesture = new MSGesture()
gesture.target = document.body
}

然后依次在document onready事件里根据touchstart, touchmove, touchend 做了一些封装和判断,然后通过 zepto 自己的事件体系来注册和触发。