zepto源码阅读-(一)基础结构分析

Posted on 2017-12-28 10:43  BiCuiOu  阅读(154)  评论(0)    收藏  举报
Zepto 是一个轻量级的针对现代高级浏览器的 JavaScript 库,是一个简化版的jQuery,简化了jQuery里很多的浏览器兼容性代码,主要针对移动端开发,它与 jquery 有着类似的 api。 如果你会用 jquery,那么你也会用 Zepto。

1.整体结构

首先,使用模块模式,先创建了一个全局的变量 Zepto,并将它指向一个匿名的自执行函数的返回值,不会污染其它的全局变量,如下

var Zepto = (function() {
})();
window.Zepto = Zepto;
window.$ === undefined && (window.$ = Zepto);

Zepto对象的定义就在这个立即调用函数里面,接下来把Zepto赋值给window:window.Zepto = Zepto。然后进行$变量名的冲突处理,如果$全局变量还没有定义,就将Zepto对象赋值给$全局变量。

把Zepto和$绑定到window下,就可以用Zepto(xxx)或者$(xxx)了。

if ( typeof define === "function" && define.amd ) {
  define( "zepto", [], function () { return Zepto; } );
}

加入以上代码,能让 zepto 支持模块化(requirejs)写法。

2.核心结构

在使用 zepto 的时候,我们常用 $ 去获取 dom ,而 dom 对象上与能调用各种方法,那么这是怎么实现的呢,$ 又从何而来。

先不关注zepto的具体逻辑实现,来看看 zepto 结构的核心部分,首先找到它的入口函数。

$ = function(selector, context) {
  return zepto.init(selector, context)
}

像使用函数一般,将我们要操纵的dom当做一个参数传入 Zepto 中,然后返回一个 zepto 对象来使用,我们传入参数,zepto 就调用 zepto.init。

zepto.Z = function(doms) {
  return new Z(doms)
}
zepto.init = function(doms) {
  var doms = ['dom1','dom2','dom3'];
  return zepto.Z(doms);
}

在以上init方法中,会获取到 dom 元素集合,这就创建了一个 zepto 集合对象,然后将集合交由 zepto.Z() 方法处理,而 zepto.Z 方法返回的是函数 Z 的一个实例。

function Z(dom, selector) {
  var len = dom ? dom.length : 0
  for (var i = 0; i < len; i++) {
    this[i] = doms[i]
  }
  this.length = dom.length;
}

函数 Z 将 doms 展开,变成实例的属性,i 为对应 domObj 的索引,并且在此处设置实例的 length 属性。

$ = function() {
  return zepto.init();
}

$.fn = {
  constructor: zepto.Z,
  method: function() {
    return this;
  }
}

从这里看出, $ 其实是一个函数,在 $ 身上又挂了很多属性和方法,而实现 $ 函数的核心是 zepto.init ,其实 $ 调用的就是 zepto.init 这个内部方法,zepto.init 最终返回的又是 zepto.Z 的结果。

$.fn是一个对象,它拥有 zepto 对象上所有可用的方法,如 addClass(), attr(),和其它方法。在这个对象添加一个方法,这样所有的 zepto 对象上都能用到该方法了。

如下是实现 zepto 里常用的index()方法的一个例子:

$.fn.index = function(element) {
  return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
}

3.神奇的$与结构之谜

那么,$又是从何而来的,相信初接触很多人都会好奇吧,因为之前没有阅读过jQuery源码,所以我一直好奇$是怎么运作的。

上面也提到了$是一个函数名,相当于:

function abc () { console.log(123); };
abc();

// 控制台输出123

function $ () { console.log(456); };
$();

// 控制台输出456

然后把$挂载到window对象里面,

所以就可以在随意位置使用,

所以在控制台直接打$或者window.$都同样输出函数:

function (selector, context) {
    return zepto.init(selector, context)
}

上面提到这个函数返回的是一个zepto对象的init初始化方法,

从这里就知道了,原来插件已经创建了zepto对象,

继续找zepto对象的init初始化方法,

init接受两个参数,这里实例化一个例子:

$("div").css("background-color","#369");

直接debug里面的东西,可以看到selector就是选择器

这里写了一些判断selector是什么,在上面的实例化里是个字符串

所以返回一个dom,用了别的方法qsa返回

接着看qsa方法里面的东西

return (element.getElementById && isSimple && maybeID) ? // Safari DocumentFragment doesn't have getElementById
        ((found = element.getElementById(nameOnly)) ? [found] : []) :
        (element.nodeType !== 1 && element.nodeType !== 9 && element.nodeType !== 11) ? [] :
          slice.call(
            isSimple && !maybeID && element.getElementsByClassName ? // DocumentFragment doesn't have getElementsByClassName/TagName
              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
          )

这个函数有点恶心,第一眼就看晕了,后面慢慢看着回过神

捋捋这里弄清两个知识点就比较明朗了:

知识点一:节点类型

var type = node.nodeType;
常量描述
Node.ELEMENT_NODE 1 一个 元素 节点,例如

Node.DOCUMENT_NODE 9 一个 Document 节点。
Node.DOCUMENT_FRAGMENT_NODE 11 一个 DocumentFragment 节点

知识点二:三元操作符

var firstCheck = false,
    secondCheck = false,
    access = firstCheck ? "Access denied" : secondCheck ? "Access denied" : "Access granted";
  
console.log( access ); // logs "Access granted"

多个三元操作符也是可能的(注:条件运算符是右结合):

ps:&&有函数的时候三元操作符判断为true