首页 > 网站 > WEB开发 > 正文

迷你版jQuery——zepto核心源码分析

2024-04-27 14:21:00
字体:
来源:转载
供稿:网友

迷你版jQuery——zepto核心源码分析

前言

zepto号称迷你版jQuery,并且成为移动端dom操作库的首选事实上zepto很多时候只是借用了jQuery的名气,保持了与其基本一致的API,其内部实现早已面目全非!艾伦分析了jQuery,小钗暂时没有那个本事分析jQuery,这里就恬不知耻说说自己对zepto的源码理解,希望对各位有用首先zepto的出现其实还是很讨巧的,他看见了巨人jQuery在移动浪潮来临时的转身慢、牵挂多的问题马上搞出了一套轻量级类jQuery框架代码,核心代码1000行不到,快速占领了移动端的市场,所以天下武学无坚不摧,为快不破啊!!!也如艾伦所言,jQuery狭义的讲其实就是dom操作库zepto将这点发扬光大,并且抛弃了浏览器兼容的包袱,甚至CSS3的前缀都不给加,这些因素造就了zepto小的事实,于是我们开始学习他吧此文只是个人对zepto的粗浅理解,有误请提出

核心组成

zepto现在也采用了模块拆分,这样读起来其实代码十分清晰,门槛也低了很多,整个zepto核心模块保持在900行以内我们说他很好的发扬了dom库特点便是因为这900行基本在干dom操作的活核心模块有以下部分组成:

① 闭包变量、工具类方法定义

这个部分主要为后面服务,比如说什么isFunction/isPlainObject/children其中有一个比较特别的变量是
zepto = {};

这个变量贯穿始终,也是zepto与jQuery很不一样的地方,jQuery是一个类,会创建一个个实例,而zepto本身就只是一个对象......

② zepto与jQuery的$

zepto第二阶段干的事情便是定义了一个类
$ = function(selector, context){  return zepto.init(selector, context)}

而我们开始便说了zepto只是一个对象,而zepto.init也仅仅是返回了一个类数组的东西,于是我们这里便看到了zepto与jQuery的惊人差异

第一观感是zepto没有类操作!我们使用$('')的操作返回的也是zepto的实例$对于zepto来说仅仅是一个方法,zepto却使用了非正规手法返回了实例......从这里看整个zepto其实和jQuery就差距大了,zepto的$方法返回了一个Object的实例,而jQuery的$返回的是真资格的jQuery对象而从后面看其实zepto也是返回的一个实例但是与jQuery的实现有所不同,那么zepto是怎么实现实例返回的呢?

③ zepto与jQuery的$.fn

我们知道jQuery的$.fn指向的是jQuery.PRototype的原型对象,而zepto的fn就是一个简单对象
$.fn = {};
zepto的第三部分便是扩展$函数,我们使用的$的方法事实上都是其静态方法,与原型链一毛钱关系都没有以上便是zepto核心模块的实现,很干净的实现,仅仅是dom操作,不涉及事件或者Ajax操作,简单来说zepto的实现是这个样子的
 1 var zepto = {}, $; 2       3 zepto.init = function (selector, context) { 4   var domArr = []; 5   //这个__proto__是系统级变量,我觉得zepto不该重置 ,但是不重置的话实例便找不到方法了!!! 6   domArr.__proto__ = $.fn 7   domArr.selector = selector; 8   //一些列操作 9   return domArr;10 };11 12 $ = function (selector, context) {13   return zepto.init(selector, context);14 };15 16 $.fn = {17   addClass: function () { },18   attr: function () { }19 };

这里有段非常关键的代码是:

domArr.__proto__ = $.fn;
如果是没有这段代码的话, domArr便是属于array的实例,便不能使用$.fn中的方法了,但是他这里重置了__proto__的指向所以就能用了PS:由于IE是不认这个属性的,所以IE必定会报错由于这里的改下,本来domArr也会有一些变化:
 1 dom.__proto__.constructor 2 function Array() { [native code] } 3  4 dom.__proto__.constructor 5 function Object() { [native code] } 6  7 zepto.Z = function(dom, selector) { 8   dom = dom || [] 9   dom.__proto__ = $.fn10   dom.selector = selector || ''11   return dom12 }13 //最后加上一句:14 zepto.Z.prototype = $.fn
如此一来,我们所有的$方法返回的东西其实就变成了zepto.Z的实例了,这里的实现原理其实也有点绕口:构造函数zepto.Z 包含一个原型 $.fn(zepto.Z的prototype被重写了)原型$.fn具有一个Constructor回值构造函数zepto.Z(这里由于其粗暴的干法其实直接指向了Object,这里关系其实已经丢失)比较不正经的是居然是通过重写__proto__实现,感觉怪怪的,好了核心模块介绍结束,我们便进入入口函数的解析了

分解$方法

$是zepto的入口,具有两个参数selector选择器与context选择范围,这里看着是两个参数,事实上各个参数不同会造成不同的实现$方法相当于一个黑盒子,用户会根据自己的想法获得自己想要的结果,这也会导致$的实现变得复杂:
 1 $('div'); 2 //=> all DIV elements on the page 3 $('#foo'); 4 //=> element with ID "foo" 5  6 // create element: 7 $("<p>Hello</p>"); 8 //=> the new P element 9 // create element with attributes:10 $("<p />", {11   text: "Hello",12   id: "greeting",13   css: { color: 'darkblue' }14 });15 //=> <p id=greeting style="color:darkblue">Hello</p>16 17 // execute callback when the page is ready:18 $(function ($) {19   alert('Ready to Zepto!')20 });

我们现在来分析其每一种实现

选择器

zepto主要干的事情还是做dom选择,这里包括标签选择、id选择、类选择等,少了sizzle的复杂,直接使用了querySelectorAll的实现真的很偷懒PS:同一个页面出现相关相同id的话querySelectorAll会出BUG,这个大家要小心处理!!!这里筛选的流程是:① 执行$(selector)方法② 执行zepto.init(selector)方法,init里面的逻辑就有点小复杂了判断selector是不是一个字符串,这里需要是干净的字符串,并且context为undefined(这里差距不大,了不起是查找范围的问题)③ 经过上述逻辑处理,高高兴兴进入zepto.qsa(document, selector)逻辑这里的逻辑比较简单直接调用判断下选择器的类型(id/class/标签)就直接使用对应的方法获取元素即可
zepto.qsa = function(element, selector){  var found,      maybeID = selector[0] == '#',      maybeClass = !maybeID && selector[0] == '.',      nameOnly = maybeID || maybeClass ? selector.slice(1) : selector, // Ensure that a 1 char tag name still gets checked      isSimple = simpleSelectorRE.test(nameOnly)  return (isDocument(element) && isSimple && maybeID) ?    ( (found = element.getElementById(nameOnly)) ? [found] : [] ) :    (element.nodeType !== 1 && element.nodeType !== 9) ? [] :    slice.call(      isSimple && !maybeID ?        maybeClass ? element.getElementsByClassName(nameOnly) : // If it's simple, it could be a class        element.getElementsByTagName(selector) : // Or a tag        element.querySelectorAll(selector) // Or it's not simple, and we need to query all    )}
View Code

创建元素

$方法的第二大功能便是创建元素了,比如我们这里的
$("<p>Hello</p>");

这里依旧会经过zepto.init的处理,判断是否具有尖括号(<),有的话便会进入神奇的fragment逻辑创建文档碎片

dom = zepto.fragment(selector, RegExp.$1, context)
这里有一个正则表达式对传入的html进行解析,目标是标签名PS:zepto对p标签的解析也会出问题,不建议使用
zepto.fragment = function(html, name, properties) {}

到fragment方法时,会传入html和那么并且会有相关属性,但是我们一般不这样干,仅仅希望创建DOM

zepto.fragment = function(html, name, properties) {  var dom, nodes, container  // A special case optimization for a single tag  if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1))  if (!dom) {    if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")    if (name === undefined) name = fragmentRE.test(html) && RegExp.$1    if (!(name in containers)) name = '*'    container = containers[name]    container.innerHTML = '' + html    dom = $.each(slice.call(container.childNodes), function(){      container.removeChild(this)    })  }  if (isPlainObject(properties)) {    nodes = $(dom)    $.each(properties, function(key, value) {      if (methodAttributes.indexOf(key) > -1) nodes[key](value)      else nodes.attr(key, value)    })  }  return dom}
View Code里面的逻辑各位自己去看,我这里不多说了,还是很简单的,大概的想法是创建一个空的div元素,将字符串装载,然后遍历div的子元素,最后返回一个node的集合数组,这个也就是我们实际需要的......这个样子,创建标签或者selector选择器得到的结果是一致的其它逻辑大同小异,我们直接就过了,zepto核心入口逻辑就到此结束了......

fn的实现

fn中包含了zepto的很多功能,要一一说明就多了去了,首先由$扩展开始说除了原型扩展外还为$包含了很多静态方法,比如什么uuid,isFunction,然后就开始了原型链扩展之路$.fn与zepto.Z.prototype指向的是同一空间,这里达到了是扩展原型链的效果这里抽2个常用API来看看,比如这里的attr
attr: function(name, value){  var result  return (typeof name == 'string' && value === undefined) ?    (this.length == 0 || this[0].nodeType !== 1 ? undefined :      (name == 'value' && this[0].nodeName == 'INPUT') ? this.val() :      (!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result    ) :    this.each(function(idx){      if (this.nodeType !== 1) return      if (isObject(name)) for (key in name) setAttribute(this, key, name[key])      else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)))    })},function setAttribute(node, name, value) {  value == null ? node.removeAttribute(name) : node.setAttribute(name, value)}
我们看到他这里直接将其转换为了元素DOM操作,没有什么好说的,只是如果value不为undefined时,里面有一个循环为属性赋值的动作再看这里的html接口
html: function(html){  return arguments.length === 0 ?    (this.length > 0 ? this[0].innerHTML : null) :    this.each(function(idx){      var originHtml = this.innerHTML      $(this).empty().append( funcArg(this, html, idx, originHtml) )    })},function funcArg(context, arg, idx, payload) {  return isFunction(arg) ? arg.call(context, idx, payload) : arg}
这里其实是先将this清空,然后装载新的dom结构,这里与设置innerHTML有所不同,appen
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表