Zepto.JS 源码的学习

前言

在学习的过程中,学会阅读源码也是一种很好的过程,但是对于一个新手来说,阅读源码就像一面茫然,下载了源码不知道从何开始.这里楼主选择从Zepto源码开始学习.首先因为Zepto并不复杂,Zepto实现功能还是比较简单, Zepto是类似于Jquery的轻量库,不过作用于手机端,类似于Jquery对DOM操作进行拼接,Zepto对PC端的IE浏览器兼容并没有做得很好,需然大小比Jquery小但是也没有Jquery做得那么好.

阅读源码需要具备什么条件

在阅读源码时需要准备些什么,你才能读懂:

  1. 在阅读源码之前你需要知道这个源码的功能和作用是什么?实现什么功能?越清楚你就越容易看懂.

  2. 在阅读源码之前你需要把源码的文档,或者测试用例看一次.

  3. 找到源码的入口,暴露在全局的入口.如果你找不准代码看起来就像一团乱麻,得找到线头入口.

  4. 读源码的时候需要,得先把源码的代码结构先弄清楚,细节的代码逻辑再慢慢整理.

$入口开始分析

在你细细看过Zepto 文档后,基本发现Zepto都是通过$使用的.$是什么?在网页脚本中导入后,$是调用Zepto的入口,我们再详细查看一下Zepto下面的方法,如下图分成了两类:

image

1
2
3
4
5

// 一类是核心方法
$.camelCase()
// 另一类是
$('<li>new list item</li>').appendTo('ul')

区别在于核心方法是挂在$对象上面的,
另一个是挂在$()生成的对象上面的.

我们可以测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE>
<html>
<head>
<script src="./zepto.js"></script>
</head>
<body>
<p id="title"><title>Zepto Demo</title></p>
<div id="ul">
<ul>
<li>One</li>
<li>Two</li>
<li>Three</li>
</ul>
</div>
<div class="span"><span>Monday</span><span>Thusday</span><span>Wesday</span></div>
</body>
</html>

输出一下下面代码

1
2
3
console.log($.prototype);
console.log($("#title"));
console.log($);

你会发现这的确很大区别的
image

Zepto源码结构

在引入Zepto后,$是一个window的一个全局变量.

Zepto核心闭包与返回$

1
2
3
4
5
6
7
8
9
10
var Zepto = (function() {
...
return $
})()

...

window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)
...

zepto核心其实是一个立即执行的闭包对象,通过定义为一个对象,赋值给window.$, 这样不会污染全局环境,防止了包里面的方法和命名冲突.

我们可以看到最后把最后把$返回给Zepto对象,那在Zepto立刻执行方法里面的$是什么呢?下面我们来一步一步实现Zepto包最简单的架构,解释$是什么.

继续接上代码

定义$变量,交给zepto.init方法处理细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 对全局暴露Zepto变量
var Zepto = (function() {
// 定义$变量
var $,

// [NEW]定义$变量,并将具体细节交给zepto.init处理
$ = function(selector, context){
return zepto.init(selector, context)
}

// 返回变量
return $
})()

// 把Zepto变量挂载在window
window.Zepto = Zepto
// 当$变量没有被占用的时候,为Zepto设置别名为$

添加zepto.init方法,添加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
27
28
29
30
31
32
33
34
35
36
// 对全局暴露Zepto变量
var Zepto = (function() {

// 定义$变量
var $,
// [NEW] 初始化zepto变量为对象
zepto = {}

// [NEW] 添加初始化方法。当selector参数为空时,则交给zepto.Z()处理
// 当selector为字符串时则把zepto.qsa(document, selector)的值存到dom变量
// 并且交给zepto.Z(dom, selector)处理
// 在源码中 zepto.init是一个过滤方法判断传入$()里面是什么参数,源码里面会有各种判断然后导入各种方法创建DOM
// 我们的简包里面只是简单判断字符型或者qsa类型

zepto.init = function(selector, context) {
var dom
if (!selector) return zepto.Z()
else if (typeof selector == 'string') {
dom = zepto.qsa(document, selector)
}
return zepto.Z(dom, selector)
}

// 定义$变量,并将具体细节交给zepto.init处理
$ = function(selector, context){
return zepto.init(selector, context)
}

// 返回变量
return $
})()

// 把Zepto变量挂载在window
window.Zepto = Zepto
// 当$变量没有被占用的时候,为Zepto设置别名为$
window.$ === undefined && (window.$ = Zepto)

添加Zepto.Z 处理dom

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 对全局暴露Zepto变量
var Zepto = (function() {

// 定义$变量
var $,
// 初始化zepto变量为对象
zepto = {}

// [NEW] 开始正式处理数据。当dom长度为0则不添加内容,
// 否则逐个将dom数组转换为实例数组
function Z(dom, selector) {
var i, len = dom ? dom.length : 0
for (i = 0; i < len; i++) this[i] = dom[i]
this.length = len
this.selector = selector || ''
}

// [NEW] 直接返回一个新的构造函数
zepto.Z = function(dom, selector) {
return new Z(dom, selector)
}

// 添加初始化方法。当selector参数为空时,则交给zepto.Z()处理
// 当selector为字符串时则把zepto.qsa(document, selector)的值存到dom变量
// 并且交给zepto.Z(dom, selector)处理
// 在源码中 zepto.init是一个过滤方法判断传入$()里面是什么参数,源码里面会有各种判断然后导入各种方法创建DOM
// 我们的简包里面只是简单判断字符型或者使用querySelectorAll类型



zepto.init = function(selector, context) {
var dom
if (!selector) return zepto.Z()
else if (typeof selector == 'string') {
dom = zepto.qsa(document, selector)
}
return zepto.Z(dom, selector)
}

// 定义$变量,并将具体细节交给zepto.init处理
$ = function(selector, context){
return zepto.init(selector, context)
}

// [NEW]使用querySelectorAll(selector)查询DOM
zepto.qsa = function(element, selector){
return selector ? element.querySelectorAll(selector) : []
}

// 返回变量
return $
})()

// 把Zepto变量挂载在window
window.Zepto = Zepto
// 当$变量没有被占用的时候,为Zepto设置别名为$
window.$ === undefined && (window.$ = 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
  // 定义$变量
var $,
// 初始化zepto变量为对象
zepto = {}
class2type = {}, // [NEW]添加类型对像
emptyArray =[] // [NEW]添加空数组


// 开始正式处理数据。当dom长度为0则不添加内容,
// 否则逐个将dom数组转换为实例数组
function Z(dom, selector) {
var i, len = dom ? dom.length : 0
for (i = 0; i < len; i++) this[i] = dom[i]
this.length = len
this.selector = selector || ''
}

// [NEW]返回类型,判断对象类型
function type(obj) {
return obj ==null ? String(obj) :
class2type[toString.call(obj)] || "object"
}

// [NEW]判断是不是window 对象
function isWindow(obj) { return obj != null && obj == obj.window }

// [NEW]判断是否数组
function likeArray(obj) {
var length = !!obj && 'length' in obj && obj.length,
type = $.type(obj)

return 'function' != type && !isWindow(obj) && (
'array' == type || length === 0 ||
(typeof length == 'number' && length > 0 && (length -1) in obj)
)
}

// 直接返回一个新的构造函数
zepto.Z = function(dom, selector) {
return new Z(dom, selector)
}


// 添加初始化方法。当selector参数为空时,则交给zepto.Z()处理
// 当selector为字符串时则把zepto.qsa(document, selector)的值存到dom变量
// 并且交给zepto.Z(dom, selector)处理
// 在源码中 zepto.init是一个过滤方法判断传入$()里面是什么参数,源码里面会有各种判断然后导入各种方法创建DOM
// 我们的简包里面只是简单判断字符型或者使用querySelectorAll类型
zepto.init = function(selector, context) {
var dom
if (!selector) return zepto.Z()
else if (typeof selector == 'string') {
dom = zepto.qsa(document, selector)
}
return zepto.Z(dom, selector)
}

// 定义$变量,并将具体细节交给zepto.init处理
$ = function(selector, context){
return zepto.init(selector, context)
}

// [NEW]给$添加type类型方法
$.type = type

// [NEW]把each 方法绑定在$上面的方法
$.each = function(elements, callback){
var i, key
if (likeArray(elements)) {
for (i = 0; i < elements.length; i++)
if (callback.call(elements[i], i, elements[i]) === false) return elements
} else {
for (key in elements)
if (callback.call(elements[key], key, elements[key]) === false) return elements
}
return elements
}

// 使用querySelectorAll(selector)查询DOM
zepto.qsa = function(element, selector){
return selector ? element.querySelectorAll(selector) : []
}

// 返回变量
return $
})()

// 把Zepto变量挂载在window
window.Zepto = Zepto
// 当$变量没有被占用的时候,为Zepto设置别名为$
window.$ === undefined && (window.$ = 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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129

// 定义$变量
var $,
// 初始化zepto变量为对象
zepto = {}
class2type = {}, // 添加类型对像
emptyArray =[] // 添加空数组


// 开始正式处理数据。当dom长度为0则不添加内容,
// 否则逐个将dom数组转换为实例数组
function Z(dom, selector) {
var i, len = dom ? dom.length : 0
for (i = 0; i < len; i++) this[i] = dom[i]
this.length = len
this.selector = selector || ''
}

// 返回类型,判断对象类型
function type(obj) {
return obj ==null ? String(obj) :
class2type[toString.call(obj)] || "object"
}

// 判断是不是window 对象
function isWindow(obj) { return obj != null && obj == obj.window }

// 判断是否数组
function likeArray(obj) {
var length = !!obj && 'length' in obj && obj.length,
type = $.type(obj)

return 'function' != type && !isWindow(obj) && (
'array' == type || length === 0 ||
(typeof length == 'number' && length > 0 && (length -1) in obj)
)
}

// 直接返回一个新的构造函数
zepto.Z = function(dom, selector) {
return new Z(dom, selector)
}


// 添加初始化方法。当selector参数为空时,则交给zepto.Z()处理
// 当selector为字符串时则把zepto.qsa(document, selector)的值存到dom变量
// 并且交给zepto.Z(dom, selector)处理
// 在源码中 zepto.init是一个过滤方法判断传入$()里面是什么参数,源码里面会有各种判断然后导入各种方法创建DOM
// 我们的简包里面只是简单判断字符型或者使用querySelectorAll类型
zepto.init = function(selector, context) {
var dom
if (!selector) return zepto.Z()
else if (typeof selector == 'string') {
dom = zepto.qsa(document, selector)
}
return zepto.Z(dom, selector)
}

// 定义$变量,并将具体细节交给zepto.init处理
$ = function(selector, context){
return zepto.init(selector, context)
}

// 给$添加type类型方法
$.type = type

// 把each 方法绑定在$上面的方法
$.each = function(elements, callback){
var i, key
if (likeArray(elements)) {
for (i = 0; i < elements.length; i++)
if (callback.call(elements[i], i, elements[i]) === false) return elements
} else {
for (key in elements)
if (callback.call(elements[key], key, elements[key]) === false) return elements
}
return elements
}

// [NEW]在原型链上面添加方法
$.fn = {
constructor: zepto.Z,
length: 0,
each: function(callback){
emptyArray.every.call(this, function(el, idx){
return callback.call(el, idx, el) !== false
})
return this
},
empty: function(){
return this.each(function(){ this.innerHTML = '' })
},
html: function(html){
return 0 in arguments ?
this.each(function(idx){
var originHtml = this.innerHTML
$(this).empty().append( funcArg(this, html, idx, originHtml) )
}) :
(0 in this ? this[0].innerHTML : null)
},
test: function(){
return this.each(function(){
console.log('Test Function');
return this
})
}
}

// 使用querySelectorAll(selector)查询DOM
zepto.qsa = function(element, selector){
return selector ? element.querySelectorAll(selector) : []
}

// [NEW] 原型链指向$.fn
// $.fn对象赋值给Z.prototype Z实例继承$.fn 对象的方法
// zepto.Z 又继承自$.fn
zepto.Z.prototype = Z.prototype = $.fn

// [NEW]$.zepto指向zepto
$.zepto = zepto

// 返回变量
return $
})()

// 把Zepto变量挂载在window
window.Zepto = Zepto
// 当$变量没有被占用的时候,为Zepto设置别名为$
window.$ === undefined && (window.$ = Zepto)

最后我们这里添加了原型链方法,each empty html test,都是定义在$.fn上面.当你调用$()生成DOM之后会返回一个Z对象,就是在Z.prototype = $.fn$.fn继承到Z对象上面的.
然之前定义的$.each()是定义在$核心构造方法上面的,两个调用的方式也不一样.

Z.prototype = $.fn继承后,为什么还要zepto.Z.prototype = Z.prototype呢?

如果我们再看源码,会发现有这样的一个方法:

1
2
3
zepto.isZ = function(object) {
return object instanceof zepto.Z
}

这个方法是用来判读一个对象是否为 zepto 对象,这是通过判断这个对象是否为 zepto.Z 的实例来完成的,因此需要将 zepto.Z 和 Z 的 prototype 指向同一个对象。 isZ 方法会在 init 中用到。

现在可以把自己创造的zepto包导入到自己定义的网页当中.你会注意到刚才自己定义的方法已经可以使用了.

image

测试一下,没问题了.

$('#test').test()

下面看一下zepto结构的图: 转至wangfupeng博客图

image

在清楚大概程序答建结构后,就可以细看分支了,可以细看每一个方法是怎样实现的.

细节实现

当你观看细节方法功能实现的时候,看到的是不一样的东西,比如是JavaScript数组的处理,全局window对象的判断,原生JavaScript方法操作等,学到的东西是不一样的,值得细细学习,不过细节方法实现我不一一详解了,我看到另一作者写得很详细的,如果有兴趣的读者可以深入研究一下,下面给出相关作者连接:

作者 yeyuqiudeng
读 Zepto 源码,分析 Zepto 源码 连接

参考连接

  1. Zepto源码分析(一)核心代码分析 地址
  2. 读Zepto源码之代码结构 地址
  3. zepto对象思想与源码分析 地址
  4. zepto源码中关于zepto.Z.prototype = $.fn的问题 地址
  5. zepto源码分析-代码结构地址
感谢你对我的支持 让我继续努力分享有用的技术和知识点.
0%