资讯专栏INFORMATION COLUMN

zepto源码分析-代码结构

sherlock221 / 321人阅读

摘要:本来想学习一下的源码,但由于的源码有多行,设计相当复杂,所以决定从开始,分析一个成熟的框架的代码结构及执行步骤。同时发表在我的博客源码分析代码结构

本来想学习一下jQuery的源码,但由于jQuery的源码有10000多行,设计相当复杂,所以决定从zepto开始,分析一个成熟的框架的代码结构及执行步骤。

网上也有很多zepto的源码分析,有的给源码添加注释,有的谈与jQuery的不同,但是都没有系统的讲解zepto框架的代码结构及初始化Zepto对象的过程

准备

默认你已经对面向对象有一定的了解,本文是边实践边写的,虽有些乱,但好处是为大家提供了分析的思路。

英文文档、 中文文档

注意在文中$变量表示一个函数对象,而$()表示执行函数,他会返回另一个对象。

从文档入手分析$

在文档中可以看到有两类方法,其中一类是没有$前缀,例如addClass,这些方法都有一个共同的特点,操作DOM或BOM。还有一类是有前缀的例如$.trim,这一类方法无关平台,只是封装了一些常用的方法,可以看作ECMA层级的方法,与浏览器无关。

我们分别打印,看以下log日志




    
    Title


    

console.log($.prototype);
console.log($("#person"));
console.log($);


结果如上图,展开绿色1即可看到所有前缀的方法,展开图中2可看到所有的不带前缀的方法。图中3返回的是一个函数。

1中的结果可以看出像$.trim这类方法保存在$.prototype的构造函数中,也就是在$中,但是$打印出来的是却一个函数,为了解决这中迷惑性,以下代码重现了这种情况,可以看出,$确实是一个函数,只是这个函数多了一些特定的方法。$.trim只是$的一个属性。

2中的方法都在对象的原型函数中,因为它执行了$()函数返回了一个对象Z,该对象的原型中包含一些类似于addClass方法。

var good = (function() {
  var g;
  var log = function(text){
    console.log(text);
  }
  g = function(){console.log("666");}
  g.log = log;
  return g;
})();
console.log(good.log("Are you OK?"));// Are you OK?
console.log(good);// function(){console.log("666");}

写到这里突然想起来console对象还有个dir方法,console.dir($)清晰明了。-_-||
补充一张动图

这种写法的好处

我认为这种写法的好处有,调用$()后返回的对象是一个新对象,就没有类似$.trim这类方法,且addClass这类方法都在原型函数中,更能节省内存。

不执行$函数对象,只是调用其函数中的特定属性,该对象只会创建一次(在引入zepto时就已经初始化了),同样不会浪费内存。

两种类型的方法共用同一个变量名,减少命名冲突的可能,封装更彻底。

下面开始自上而下的分析源码,层层剥离,使脉络清晰。

一、闭包返回与全局变量
var Zepto = (function() {
  return $
})()

window.Zepto = Zepto
window.$ === undefined && (window.$ = Zepto)

吐槽一下源码中不写分号,总感觉怪怪的。

使用自执行匿名函数返回$传递给Zepto。然后把Zepto$作为window的属性。

这样对外只有两个或一个变量可以使用,不会污染全局环境,如果命名冲突,只需改源码中最后两行即可。

二、核心架构
var Zepto = (function() {
    var $,zepto = {};
    $.trim = function(str) {
        return str == null ? "" : String.prototype.trim.call(str)
    }
    $ = function(selector, context){
        return zepto.init(selector, context)
    }
    $.fn = {
        addClass: function(name){
            // 省略
        }
        // 省略
    }
    zepto.Z.prototype = Z.prototype = $.fn
    $.zepto = zepto
    return $
})()

在此可以看到$.trim与addClass与其他变量或属性的关系,去除这两个属性后就是第二层的架构,如下。

var $,zepto = {};

初始化了一个$变量和zepto对象,注意这里是小写

$函数

这个函数调用zepto.init方法,返回对象,在之后会讲解。

添加$.fn对象

它拥有Zepto对象上所有可用的方法(官方文档),这里可能有误解,应该是拥有由$()返回的对象的所有方法,里面的方法在$("#person").prototype中看到过

zepto.Z.prototype = Z.prototype = $.fn

Z.prototype = $.fn如果你仔细观察开始时的$("#person")返回的对象其实就是Z,那么经过$()返回的对象的原型指向了拥有大量方法的$.fn对象,所以才可以在$("#person").prototype中看到过addClass方法

然后是zepto.Z.prototype = $.fn,请参考zepto源码中关于zepto.Z.prototype = $.fn的问题

$.zepto = zepto

不知道为什么有这一句,似乎是可以通过$.zepto访问内部的方法,例如$.zepto.isZ($("#person"))。又或许是想将其封装为$的属性。

return $

可以清楚的看到内部的结构,$.fn$.zepto$.trim都作为$对象的属性存在,如果调用$()函数,返回的Z对象就拥有指定的原型链Z.prototype = $.fn

那么问题又来了:zepto.init方法是做什么的?执行$()函数返回的是什么对象?

三、框架的入口:zepto.init

还是先上源码

$ = function(selector, context){
    return zepto.init(selector, context)
}
zepto.init = function(selector, context) {
    var dom
    if (!selector) return zepto.Z()// 如果是$()或$("")则执行
    else if (typeof selector == "string") {// 如果传入的是字符串
        selector = selector.trim()// 去除收尾空白符
        if (selector[0] == "<" && fragmentRE.test(selector))// 如果传入的字符串是以<开头且符合HTML代码规则(用了正则表达式),即创建元素
            dom = zepto.fragment(selector, RegExp.$1, context), selector = null// 创建一个DOM对象
        else if (context !== undefined) return $(context).find(selector)// 这里其实是一种讨巧的办法,我相信jQuery中肯定不会这么写,目的是实现在指定范围内查找[context]元素
        else dom = zepto.qsa(document, selector)// 调用zepto.qsa解析字符串,返回一个DOM数组
    }
    else if (isFunction(selector)) return $(document).ready(selector)// 很简单,如果是函数,则在文档就绪后执行
    else if (zepto.isZ(selector)) return selector// 如果是一个zepto对象,直接返回
    else {
        if (isArray(selector)) dom = compact(selector)// 如果是数组,调用compact返回一个数组,最后经Z变成类数组对象,我想这里是把几个DOM对象作为数组的参数传入,返回一个类数组对象
        else if (isObject(selector))// 如果是一个对象,将其包含在数组之内,如p = document.getElementById("#p");$(p);
            dom = [selector], selector = null
        else if (fragmentRE.test(selector))// 不知道是干嘛的
            dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
        else if (context !== undefined) return $(context).find(selector)
        else dom = zepto.qsa(document, selector)
    }
    return zepto.Z(dom, selector)// 可以看这里,无论以上过程经历了什么,都要经过此函数,目的是将数组转化为类数组对象。
}
zepto.Z = function(dom, selector) {
    return new Z(dom, selector)
}
/**
 * 一个构造函数,将dom对象中的属性和方法都复制到this下,并添加了两个属性,length和selector,这个函数的目的是将DOM对象转化为供zepto使用的类数组对象
 */
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.fragment = function(html, name, properties) {
    // 这里的代码就不展开了,其作用是返回一个DOM对象,若$()中传入第二个参数,则将其属性添加给创建的DOM对象
    return dom
}
zepto.qsa = function(element, selector){
    // 这里也不展开代码,又兴趣的可以直接看源码,很简单,无非是根据传入的选择符分别调用getElementByID、getElementsByTagName、getElementsByClassName、querySelectorAll等方法,返回一个数组,数组的值即是DOM对象,这就是最核心的选择器,有点坑爹。
}

这一段代码是zepto的核心代码,是使用zepto的入口,这里还是从文档入手比较好理解。

Zepto集合是一个类似数组的对象,它具有链式方法来操作它指向的DOM节点,除了$(Zepto)对象上的直接方法外(如$.extend),文档对象中的所有方法都是集合方法。

上一句可以告诉我们:Zepto集合是一个类似数组的对象即是之前$("#person")返回的对象。文档中所有不带前缀的方法叫做集合方法

再来看方法的调用

$(selector, [context])      // 在指定范围内查找[context]元素,类似$(context).find(selector),例如$(".logo",".header"),只选取.header类中的.logo类
$()       // 这里应该是指传入zepto对象,如:var a = $("a");$(a);
$()              // 选取所有页面中的div元素,如:$("div")
$(htmlString)               // 创建一个元素,如:$("

Hello

") $(htmlString, attributes) // 创建带有属性的元素,$("

", { text:"Hello", id:"greeting", css:{color:"darkblue"} }) Zepto(function($){ ... }) // 当页面ready的时候,执行回调 // 还有写文档中没有 $()或$("")

至于为什么调用Z()函数返回的对象都以Z为对象名呢?看这段小代码就可以明白

function Z(){
  this.name = 666;
}
z = new Z();
console.log(z);// 返回的对象名为Z

这个过程比较复杂,建议你亲自动手试一试,还是以$("#person")为例,用Chrome在这一行打断点,然后步进,我看能不能做个flash图。

如果你仔细观察,这一层的核心源码最后大部分都有return返回,最终$()也会返回对象,整个过程其实是对向$()中传入的参数进行处理运算,最终返回一个zepto自己创造的对象,然后用于后续操作。

总结

zepto的源码对一般的熟练面向对象的人来说是非常简单的,对于有面向对象概念没有写过的人来说是那种踮起脚尖能得到的难度。最开始想学习jQuery源码,但看了一点觉得太复杂,于是投机取巧看zepto,也是完全理不清头绪啊,知道上个星期天找到了一种方法,写一端小代码,然后在Chrome里步进调试,看函数之间的依赖关系法,看函数的传入值,返回值,了解这个函数是做什么用的。最后慢慢的理清头绪。这篇文章在星期一就开始写,一直到星期四才算完成。

$(或Zepto)是一个函数对象,但他包含了一些特定的属性(方法)。可以直接调用这些属性(方法),这些属性(方法)大都与浏览器无关。也可以执行$函数,执行后返回一个类数组对象,这个对象的原型中包含一些操作DOM的方法,向原型中添加属性(方法),所有的对象都可以访问到。执行$函数是zepto的关键代码,其目的是根据传入函数的变量值,加工处理成类数组对象并返回,用于后续操作。

同时发表在我的博客:《zepto源码分析-代码结构》

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/80927.html

相关文章

  • Zepto源码代码结构

    摘要:源码结构整体结构如果在编辑器中将的源码折叠起来,看到的就跟上面的代码一样。参考源码分析代码结构对象思想与源码分析设计和源码分析源码中关于的问题最后,所有文章都会同步发送到微信公众号上,欢迎关注欢迎提意见 虽然最近工作中没有怎么用 zepto ,但是据说 zepto 的源码比较简单,而且网上的资料也比较多,所以我就挑了 zepto 下手,希望能为以后阅读其他框架的源码打下基础吧。 源码版...

    warkiz 评论0 收藏0
  • Zepto 源码分析 1 - 进入 Zepto

    摘要:选择的理由是一个用于现代浏览器的与大体兼容的库。环境搭建分析环境的搭建仅需要一个常规页面和原始代码一个常规页面打开的首页即可,在开发人员工具中即可使用原始代码本篇分析的代码参照,进入该代码分支中即可。 选择 Zepto 的理由 Zepto is a minimalist JavaScript library for modern browsers with a largely jQue...

    Aklman 评论0 收藏0
  • Zepto 源码分析 4 - 核心模块入口

    摘要:对象构造函数读入的两个参数与在中也有明确的规范,用以保证构造函数的简单性。 承接第三篇末尾内容,本篇结合官方 API 进入对 Zepto 核心的分析,开始难度会比较大,需要重点理解几个核心对象的关系,方能找到线索。 $() 与 Z 对象创建 Zepto Core API 的首个方法 $() 按照其官方解释: Create a Zepto collection object by pe...

    xzavier 评论0 收藏0
  • Zepto源码之Gesture模块

    摘要:模块基于上的事件的封装,利用属性,封装出系列事件。这个判断需要引入设备侦测模块。然后是监测事件,根据这三个事件,可以组合出和事件。其中变量对象和模块中的对象的作用差不多,可以先看看读源码之模块对模块的分析。 Gesture 模块基于 IOS 上的 Gesture 事件的封装,利用 scale 属性,封装出 pinch 系列事件。 读 Zepto 源码系列文章已经放到了github上,欢...

    coolpail 评论0 收藏0
  • Zepto 源码分析 3 - qsa 实现与工具函数设计

    摘要:承接第一篇末尾内容,本部分开始进入主模块,分析其设计思路与实现技巧下文代码均进行过重格式化,但代码版本同第一部分内容且入口函数不变的选择器先从第一个与原型链构造不直接相关的工具函数说起,观察的设计思路。 承接第一篇末尾内容,本部分开始进入 zepto 主模块,分析其设计思路与实现技巧(下文代码均进行过重格式化,但代码 Commit 版本同第一部分内容且入口函数不变): Zepto 的选...

    ctriptech 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<