首页 > 代码库 > zepto源码学习-01-整体感知
zepto源码学习-01-整体感知
在公司一直做移动端的项目,偶尔会做点PC端的东西,但基本上都是和移动端打交道,移动端嘛必须上zepto。
简单介绍下Zepto,它是一个面向高级浏览器的JavaScript框架的,实现JQuery的大部分API,尤其针对手机上web开发(流量金贵,JQ又太重了,体积太大,考虑太多性能不好),因此选择Zepto.js一个非常不错的选择!纵观各大网站选用zepto的特别多。
做移动端为了让页面更轻巧,大多都是自己写原生代码,遇到难题,我一般都是去看zepto的实现,把其中的一些搬到自己代码中,久而久之、对zepto就熟悉了。
进入正题:zepto官网 地址
再上一张图,一个大神画的,不是我画的。
这张图基本上包含了zepto的api,并且进行了分类。
在该github上下载zepto的源码也是进行模块划分的
在官网上下载的zepto默认导入了其中五个个模块
从github上下载下来的代码可以看到有很多个文件,其中的核心文件就是zepto
先从这个文件入手,看下这个文件903行,声明了一个全局变量Zepto,照理说Zepto已经挂载到window上了,这里为什么还window.Zepto = Zepto再次进行赋值,不解!
window.$ === undefined && (window.$ = Zepto)
windwo上的$对象没有被占用就直接赋值为Zepto,我们平时使用$(XXX)的时候也可以替换成Zepto(XXX),但是没有必要,多写四个字符不累啊!
var Zepto=(function(){XXXX})();
Zepto后面是一个立即执行函数,内部肯定返回了某个对象然后赋值给Zepto
所以我展开立即执行函数,反倒最下面看到以下代码
//把$.fn赋值给Z.prototype、zepto.Z.prototype zepto.Z.prototype = Z.prototype = $.fn // Export internal API functions in the `$.zepto` namespace zepto.uniq = uniq zepto.deserializeValue = deserializeValue $.zepto = zepto //返回内部的$对象 return $
其他的代码先不管什么意思,反正这里返回了$对象,那么我们平时使用的全局$其实就是内部的$,最后看下内部哪里声明的$这个东西
$ = function(selector, context) { return zepto.init(selector, context) }
发现内部的$其实就是一个函数,内部是return zepto.init(selector, context)
我们平时写的$(XXX) 其实就是调用的zepto.init(XX)
再次回到原点,平时我们都是怎么使用$ 都有哪些用法,JQuery的$好像有八九用法
1. jQuery([selector,[context]])2. jQuery(element)3. jQuery(elementArray)4. jQuery(object)5. jQuery(jQuery object)6. jQuery(html,[ownerDocument])7. jQuery(html,[attributes])8. jQuery()9. jQuery(callback)
zepto总结下也就三种
1、$(selector,context?) 传入一个选择器返回一个zepto对象
2、$(function(){}) 传入一个函数,dom ready时执行
3、$(html,attrs?) 传入一个html字符串,构建元素,返回一个或zepto对象
4、$(dom obj)传入dom对象返回zepto对象
以上操作最后都是调用zepto.init(selector, context),我们的重点就是分析zepto.init这个函数
zepto.init = function(selector, context) { var dom // If nothing given, return an empty Zepto collection // 如果啥都没传直接返回一个空的Zepto集合 if (!selector) return zepto.Z() // Optimize for string selectors // 如果第一个参数是string else if (typeof selector == ‘string‘) { //去掉空格 selector = selector.trim() // If it‘s a html fragment, create nodes from it // Note: In both Chrome 21 and Firefox 15, DOM error 12 // is thrown if the fragment doesn‘t begin with < // 如果是一个HTML片段,创建节点注意,在chrome21和FF15版本, // DOM错误12不是以<被抛出 if (selector[0] == ‘<‘ && fragmentRE.test(selector)) dom = zepto.fragment(selector, RegExp.$1, context), selector = null // If there‘s a context, create a collection on that context first, and select // nodes from there //如果存在一个上下文环境,在这个上下文对象中去选择节点 else if (context !== undefined) return $(context).find(selector) // If it‘s a CSS selector, use it to select nodes. // 如果是一个css选择器,用它来选择节点 else dom = zepto.qsa(document, selector) } // If a function is given, call it when the DOM is ready // 如果一个函数存在,在domready就绪后触发 else if (isFunction(selector)) return $(document).ready(selector) // If a Zepto collection is given, just return it // 如果zepto已经收集给出,直接返回 else if (zepto.isZ(selector)) return selector else { // normalize array if an array of nodes is given // 如果节点已经为数组,进行聚合 if (isArray(selector)) dom = compact(selector) // Wrap DOM nodes. // 包装DOM节点 else if (isObject(selector)) dom = [selector], selector = null // If it‘s a html fragment, create nodes from it // 如果是一个HTML片段,对该片段创建节点 else if (fragmentRE.test(selector)) dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null // If there‘s a context, create a collection on that context first, and select // nodes from there else if (context !== undefined) return $(context).find(selector) // And last but no least, if it‘s a CSS selector, use it to select nodes. else dom = zepto.qsa(document, selector) } // create a new Zepto collection from the nodes found // 对发现的节点创建一个新的Zepto集合,这里把查询到dom 对象和selector 传递给了zepto.Z return zepto.Z(dom, selector) }
最后返回的是zepto.Z(dom, selector),最终我们还得看zepto.Z的实现
zepto.Z = function(dom, selector) { return new Z(dom, selector) }
其实相当于一个工厂方法,在这里new了一个Z,外面每次我们调用$ 最后拿到的都是新的实例对象,但是我们并没有new,其实就是这里帮我们new了,JQuery也这样,这种思想到处都有用到。
最后我们还得参看Z的实现
function Z(dom, selector) { var i, len = dom ? dom.length : 0 //把dom对象数组放到this上,所以我们外部可以XX[0]得到一个原生的dom对象,关键就在这里,this是一个伪数组 for (i = 0; i < len; i++) this[i] = dom[i] //把长度赋给this.length this.length = len //这个就不说了 this.selector = selector || ‘‘ }
直接返回了一个Z的实例对象。先看一个demo
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>test zepto</title> <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-cacle=1.0,maximum-scale=1.0"> <meta content="telephone=no" name="format-detection"> <meta content="email=no" name="format-detection"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black"> <style> </style></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> <script type="text/javascript" src="../zepto-full-1.1.6.js"></script> <script> var s=$(‘#items‘); console.log(s) </script></body></html>
这个demo 我们得到的test1对象如下
按之前的分析Z对象有length ;;Z[0]、Z[1]…… 伪数组;还有selector
但是现在Z的原型指向了zepto.z[0];所以返回的对象我们可以test1.attr()、test1.addClass()、test1.after()我们可以访问这些方法,是因为这个实力对象的原型能够访问到这些方法。那么问题来了,Z的原型什么时候指向zepto.z[0]
带着这个疑问,继续查看源码,发下如下代码
//把$.fn赋值给Z.prototype、zepto.Z.prototype zepto.Z.prototype = Z.prototype = $.fn // Export internal API functions in the `$.zepto` namespace zepto.uniq = uniq zepto.deserializeValue = deserializeValue $.zepto = zepto //返回内部的$对象 return $
$.fn下的所有方法都挂在了zepto.Z的prototype和Z.prototype下,也就是说上面的test1已经拥有了$.fn的所有方法。$.fn上的方法也就是$的实例方法,$上还有一大堆静态方法,不用创建一个$对象也可以调用。
我使用的版本是1.1.6,以前的版本 在这里实现略有不同,但是大同小异,搞懂了原型、原型链、继承这些都不是问题。
细心的话会发现一个问题,test的__proto__ 指向的是zepto.z[0], 试问问为什么会是zepto.z[0]这个东西,感觉怪怪的。
为什么会是zepto.z[0]、zepto.z[0]是什么东西,下回分解!!
zepto源码学习-01-整体感知