Zepto核心模块源代码分析

1、Zepto核心模块架构

Zepto核心模块架构图

该图展现了Zepto核心模块架构代码的组织方式。主要分为私有变量、函数和暴露给用户的全部api。javascript

Zepto核心模块架构代码

该图展现了Zepto的核心模块架构代码,忽略了全部实现的细节。css

var Zepto = (function() { // 私有变量($和zepto不是私有变量,它们会被暴露出去) var undefined, emptyArray = [], filter = emptyArray.filter, slice = emptyArray.slice, $, zepto = {}; // 私有函数 function likeArray() {} // Z类 function Z() {} // 构建Z对象的主要函数 zepto.matches = function() {}; zepto.fragment = function() {}; zepto.Z = function() { return new Z(dom, selector) }; zepto.isZ = function() { return object instanceof zepto.Z }; zepto.init = function() {}; zepto.qsa = function() {}; // Z对象的共享方法 $.fn = { constructor: zepto.Z, length: 0, forEach: emptyArray.forEach, reduce: emptyArray.reduce, push: emptyArray.push, sort: emptyArray.sort, splice: emptyArray.splice, indexOf: emptyArray.indexOf, concat: function() {} } // 静态方法 $.extend = function() {}; // plugin compatibility $.uuid = 0 $.support = {} $.expr = {} $.noop = function() {} // 修改zepto.Z和Z的原型都指向$.fn zepto.Z.prototype = Z.prototype = $.fn // 把内部的一些API函数经过$.zepto命名空间暴露出去 zepto.uniq = uniq zepto.deserializeValue = deserializeValue $.zepto = zepto return $ })() window.Zepto = Zepto window.$ === undefined && (window.$ = Zepto)

Zepto核心模块架构说明

经过上面两张图,咱们能够发现Zepto核心模块整个架构是很是的简单整洁。首先Zepto对象经过执行一个当即调用函数进行赋值,所以Zepto对象的定义就在这个当即调用函数里面,接下来把Zepto赋值给window:window.Zepto = Zepto。而后进行$变量名的冲突处理,若是$全局变量尚未定义,就将Zepto对象赋值给$全局变量:window.$ === undefined && (window.$ = Zepto)html

接下来分析一下当即调用函数里面的代码。一开始定义了一些私有变量和私有函数,这些变量和函数为其余代码服务,不会暴露给用户($zepto变量例外,zepto会经过$属性的形式暴露出去,即等于$.zepto),其中最重要的一个函数为Z类,调用$函数生成的对象都是Z类的实例,我称其为Z对象。而后就是$.zepto对象的一些方法,主要分为两类,第一类:fragmentZisZinitqsa,这些方法是生成Z对象的主要方法,用户能够经过重写这些方法,来实现兼容低版本的浏览器。$.zepto对象的第二类方法:matchesuniqdeserializeValue,这些方法其实就是一些静态方法。接下来就是Z类的原型对象:$.fn,全部Z对象的共享方法都保存在这里。其实到这里$.fn和Z类一点关系都没有,可是在当即调用函数的最下面有这么一行代码:zepto.Z.prototype = Z.prototype = $.fn,经过将Z.prototype指向$.fn对象就可使$.fn成为真正Z类的原型对象。再下来就是一些静态方法$.extend和插件相关的代码。最后返回$并赋值给Zepto。java

Zepto核心模块架构细节

在Zepto架构图中,我把lengthforEachreducepushsortspliceindexOf这几个属性放到了一齐,这是由于它们有特别的用途。当浏览器检测到一个对象有以上属性时,就把它们看成是类数组,从而当你在浏览器的控制台打印它们时,它们将会以数组的形式展示出来,而不是以对象的形式。实际上,在chrome浏览器中,只要有length和splice属性,对象就会被当成是类数组:git

你们也可能留意到,我上面说过生成Z对象的是Z类,它是一个私有函数,可是为何$.fn.constructor要指向$.zepto.Z?还有就是$.zepto.Z的原型也要修改成$.fn?这实际上是由于$.zepto.isZ方法会经过$.zpeto.Z来判断是否Z对象,而非Z函数:object instanceof zepto.Z,因此经过使Z函数和zepto.Z的函数的原型保持一致,就能够经过zepto.Z来判断是否Z对象了。可是为何不直接经过Z函数来判断是否Z对象呢:object instanceof Z?缘由在于用zepto.Z方法能够被用户改写,成为新的构造函数。因此若是一旦用户改写了$.zepto.Z方法以后,而zepto.isZ仍是经过Z类来判断Z对象,那就会致使永远都返回false了。github

2、获取Z对象

获取Z对象的流程图

获取Z对象的流程说明

其实$函数里面什么也没干,只是单纯的调用$.zepto.init方法:return zepto.init(selector, context)。因此$.zepto.init方法里面的逻辑就是获取Z对象的逻辑。在$.zepto.init方法内部经过不一样的参数执行不一样的流程,最后返回一个Z对象或者在DOMContentLoaded事件触发后执行传进来的回调函数。具体会分为如下几种状况:ajax

  • 有传入context,回调自身:$(context).find(selector)
  • selector参数为空,直接调用$.zepto.Z方法获取Z对象:zepto.Z()
  • selector参数为html片断,调用$.zepto.fragment方法获取对应DOM节点再调用$.zeptoZ方法获取Z对象
  • selector参数为css选择器,调用$.zepto.qsa方法获取对应DOM节点再调用$.zepto.Z方法获取Z对象
  • selector参数为DOM节点数组,去掉数组中值为null的项,调用$.zepto.Z方法获取Z对象
  • selector参数为单个DOM节点,dom = [selector],而后调用$.zepto.Z方法获取Z对象
  • selector参数为Z对象,直接返回该Z对象
  • selector参数为函数,执行$(document).ready(selector),在DOM加载完的时候调用该函数

此外上面说起到的调用$.zepto.Z方法只是简单的new一个Z对象:return new Z(dom, selector),Z类里面会把dom和selector保存起来,供其余方法使用。Z类的代码也很简单:正则表达式

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 || '' }

$.zepto.qsa方法说明

$.zepto.qsa方法是Zepto的css选择器实现,很是重要,可是从上图能够看出代码实现不复杂,主要就是利用原生的element.querySelectorAll方法实现。只不过对selector参数添加了一些判断,对简单的id、class和标签名使用效率更好的原生方法实现。chrome

$.zepto.fragment方法说明

$.zepto.fragment方法的主要功能是根据给出的html片断生成对应的DOM节点。实现的大概思路是生成一个临时的父元素,而后把html片断参数赋值给父元素的innerHTML,这样就能够生成html片断对应的节点。其中须要注意的是临时的父元素在大部分状况之下应该是一个div节点,可是当html片断是表格相关的元素时,父元素就不能是div节点。例如html片断参数本该生成的是tr节点,因此它的临时父元素就应该是tbody节点。这是由于若是表格元素没有添加到正确的父元素里面,会致使不能生成正确的节点。api

3、经常使用共享函数解释

$.fn.ready

Zepto的经常使用功能之一就是在$函数里面传入一个回调函数,把代码都写在该回调函数里面,保证代码在DOM加载完以后才执行,例如:$(function(){})。其实该方法是调用$.fn.ready完成的,前面的代码等价于:$(document).ready(function(){})

该方法先根据document.readyState的值判断DOM是否加载完成,完成就直接触发传进来的回调函数。不然在DOMContentLoaded事件触发的时候调用该回调函数。

$.fn.show

$.fn.show方法也是咱们用得很是多的一个方法,实现逻辑为:一、内联样式element.style.display值为'none'的话就设置为空字符串''。二、若是是在非内联样式设置了display:none,那就先获取对应元素的默认display值,赋值给element.style.display。而获取元素的默认值就经过新建一个同名标签节点,而后获取它的display值就等于该元素的默认display值了。此外,获取默认值涉及到DOM的操做,速度比较慢,因此每次获取到的值都会保存到私有变量elementDisplay中缓存起来,之后要获取同类标签默认值的时候就能够在缓存里面获取。

$.fn.addClass

$.fn.addClass的实现思路大概是:一、判断元素是否有对应类名。二、若是没有的话就直接添加到元素的className中。

须要展开的就是如何判断是否有对应类型,Zepto的作法就是经过类名建立能够正则表达式来进行判断,建立正则表达式的函数以下:

funcion classRE(name) { return name in classCache ? classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)')) }

4、链式调用

使用Zepto过程当中最为经常使用的特性应该就是链式调用了,例如:$('.outer-div').find('.inner-div').addClass('div')。这种功能其实很容易实现,具体方法就是在$.fn中的共享方法里面返回自身this就能够了。下面为一个简单的例子:

$.fn.sayHi = function() { alert('hi'); return this; }

5、功能扩展

Zepto按功能划分把代码分散到不一样的js文件里,默认下载的话包含zepto、event、ajax、form 和 ie这几个模块,其中zepto就是本文所介绍的核心模块。此外Zepto还提供fx、deferred、touch等可选模块,你们能够根据本身的需求构建本身所须要的Zepto,具体方法能够看官网的Github

而合并各模块的方法也很简单,只要给$对象和$.fn对象添加方法就完成了静态方法和共享方法的扩展,例子以下:

;(function($){ $.Sayhi = function(){}; // 扩展静态方法 $.fn.sayhi = function(){}; // 扩展共享方法 })(Zepto)

6、结束语

Zepto核心模块的分析到此结束,具体的实现细节你们能够到个人Github查看我作的详细注释。Zepto核心代码量只有1000行左右,仍是很值得你们花点时间去研究一下的。接下来我也会分析每个模块,而且分享出来。若是你们发现了这篇文章的错漏之处,能够在下面作出留言,我会尽快回复 :-D

相关文章
相关标签/搜索