关于 Zepto 源码结构分析的文章已经很多了,本文主要从两点,即图示和详细步骤跟踪上,对其进行分析。
首先还是上源码:
外层结构源码
var Zepto = (function() {
})()
window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)
复制代码
外层结构很简单,无非是执行一个立即执行函数,把返回结构赋给 Zepto
,再把 Zepto
绑定到全局。最后,如果全局的美元符号 $
没有被占用,则把 Zepto
赋给 $
。很多文章里都已经讨论过了,不再细说。
核心结构源码
var zepto = {}, $
function Z(doms) {
var len = doms.length
for (var i = 0; i < len; i++) {
this[i] = doms[i]
}
this.length = doms.length
}
zepto.fragment = function(html, name, properties) {
// 根据 html 字符串生成 dom
}
zepto.isZ = function(object) { return object instanceof zepto.Z}
zepto.Z = function(doms) {
return new Z(doms)
}
zepto.init = function(selector, context) {
var dom
// 根据 selector 生成 dom
// ···
return zepto.Z(dom, selector)}
$ = function(selector, context){
return zepto.init(selector, context)
}
zepto.qsa = function(element, selector){
// 根据 选择器字符串生成 dom
}
$.type = type
······
// 把各种工具方法绑定到 $ 上
$.fn = { constructor: zepto.Z,
forEach: emptyArray.forEach ······
// 把各种实例方法绑定到 $.fn 上
}
zepto.Z.prototype = Z.prototype = $.fn
$.zepto = zepto
return $复制代码
以上代码是抽取主要部分后得到的骨干,饶是如此,结构就已经很复杂了。对象和原型之间互相赋值和引用,函数之间互相调用,楞一遍看下来让人非常晕。
我们现在就来整理一下。
首先,这里有三个最主要的对象,这三个对象就是:
$
, zepto
, Z
。注意,这里的 zepto
首字母是小写,不要和外层结构的 Zepto
搞混了。
下面就对这三者之间的关系进行分析。
$, zepto, Z 关系分析
仔细看源码,分析三者以及它们之间的关系,我们发现以下几个事实:
- 内部结构最终返回的是
$
,也就是说其实就是$
被赋给了外层的Zepto
。 $
本身是一个函数,它调用了zepto.init
方法 并返回。zepto.init
函数做了很多操作,其中最后一步调用了zepto.Z
。Z
是一个构造函数。zepto.Z
方法调用构造函数Z
,返回一个Z
的实例。zepto
对象被赋给了$.zepto
属性。$.fn
被赋给了zepto.Z
和Z
的原型。也就是说后两者的原型就是$.fn
。
结合图例仔细看几遍,$
, zepto
, Z
, zepto.Z
, zepto.init
, $.fn
之间的关系应该就清晰多了。
至于图中出现的 zepto.qsa
和 zepto.fragment
,那是在 zepto.init
方法中被调用的重要方法,所以一并也画进去了,对整体结构没有影响。
执行详细过程
虽然知道了源码内部三个主要对象的关系,但是源码又是如何起作用的呢?
我们知道,Zepto 使用时都是以选择器为开端的。当我们使用 $()
来生成一个 Zepto 对象时,内部究竟发生了什么?现在我们就从源码入手开始分析。
- 进入
$
方法,内部调用zepto.init
方法。 - 进入
zepto.init
方法。该方法判断传入的selector
参数的类型,对其做相应的处理。- 如果是 html 字符串,就调用
zepto.fragment
方法。 - 如果是选择器字符串,就调用
zepto.qsa
方法。 - 当
selector
参数是其他类型时(对象、数组、Zepto 实例、函数······),做其他的相应处理。 - 最终调用
zepto.Z(dom, selector)
对生成的 dom 做一层包裹。
- 如果是 html 字符串,就调用
- 进入
zepto.Z
方法。该方法通过new Z(dom, selector)
来生成一个Z
的实例并返回。 - 进入构造函数
Z
。Z
的内部很简单,仅仅是对传入的 dom 做一个简单的循环拷贝,并复制了它的length
,生成了一个类数组对象。这个对象就是一开始$()
方法最终返回的对象。
至此,经过层层递进的分析,我们得到了结论:$()
返回的就是一个 Z
的实例。
前面代码的倒数第三行有 zepto.Z.prototype = Z.prototype = $.fn
。这行代码非常重要,它让 Z
的 prototype
指向了 $.fn
,这也是为什么 Z
的实例可以调用 $.fn
中的一大堆方法。
至于这个等式的前半部分,zepto.Z.prototype = Z.prototype
,大概是由于 $.fn
的 constructor
被指给了zepto.Z
。为了让 $() instanceof Zepto.zepto.Z
成立,所以才有这一等式。
不过,我认为这多少有点违背人的直觉,也许让 $.fn
的 constructor
指向 $
本身更好。毕竟在 jQuery 中,$() instanceof jQuery === true
,但是在 Zepto 中,$() instanceof Zepto === false
。
至于这个设计的优劣,又是另一个话题了,大家可以讨论。