zepto v1.2.0源码核心解析

本文介绍 Zepto.js 的核心实现原理,包括其结构、工具函数及主要功能的实现方式。Zepto 是一个轻量级 JavaScript 库,兼容 jQuery API,适用于现代浏览器。

什么是zepto?

Zepto是一个轻量级的、针对现代高级浏览器的JavaScript工具库,它兼容jQuery的API。使用方式和jQuery类似,只需将其 js 文件引入即可使用其内置方法,提高 js 开发效率

关于本文

由于 Zepto 全部源代码内容较多,这里暂时只讨论关于 Zepto 的核心函数 Zepto / $ 函数的实现原理,虽然设计内容不多,但只要理解其核心设计思路后,便可自己着手实现一个类似的 js 工具库。加之其余代码更多是对其核心功能的扩展,因此在学习时可选择性忽略

关于完整源代码可以在 Zepto 中文网获得: https://www.zeptojs.com.cn/

外层结构

最外层使用立即执行函数包裹核心函数 Zepto 。最开始使用 define 检查是否存在 require 依赖项,如果存在则使用define定义一个并返回并执行一个传入global 参数的factory函数,否则直接执行factory函数,并用global作为参数

(function(global, factory) {
    if (typeof define === 'function' && define.amd){
      define(function() { return factory(global) })
    }
    else
      factory(global)
    }(this, function(window) {
    var Zepto = (function() {
    ...
    })()
    window.Zepto = Zepto
    window.$ === undefined && (window.$ = Zepto)
    ...
    return Zepto
}))

注: global此时为window,factory 为第7行的函数

网络上关于第3行代码具体作用的相关回答:

此代码检查是否存在 require.js,即JavaScript依赖项管理库。
如果'define'不是未定义的,并且它是一个函数,并且'amd'(异步模块定义)也被定义了,那么代码假定require.js正在运行。
如果是这样,那么它定义'工厂'并将jQuery作为依赖项传递给它。否则,它通过将代码附加到根对象来设置代码所需的依赖关系。
至于'工厂'是什么:它不是由Javascript框架定义的,它最有可能是同一个文件中的一个函数。它将采用参数jQuery。

注册全局函数

使用 window 全局对象注册 Zepto 函数,并为此函数注册别名 $,函数执行完毕后返回 Zepto 接受用户输入的参数

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

核心函数结构

内部代码量比较多,总体可分为三大部分

第一部分:外层变量的声明和定义

第二部分:工具类函数的声明和定义

第三部分: $ / Zepto 函数的对象实现

其中,第三部分为重点,第一二部分会在分析第三部分挑选关联项说明

init

  1. 使用 window.Zepto() 调用全局函数(window 可省略)后返回 $ 函数, 也可直接 $() 调用 $ 函数;

  1. 执行后返回 zepto.init 函数处理传入参数

$ = function(selector, context){
   return zepto.init(selector, context)
}
// 此函数对传入参数进行校验,根据适合的参数类型返回对应的处理函数或执行相应操作
zepto.init = function(selector, context) {
      // 创建空的用于接收 dom 的变量
      var dom
      // 如果没有给定参数, 返回一个空的 Zepto 集合。关于 zepto.Z 的详细信息在下方给出
      if (!selector) return zepto.Z()
      // 优化字符串选择器
      else if (typeof selector == 'string') {
        // 清除两端空格
        selector = selector.trim()
        /*
            判断是否为 html 标签, 如果为真则使用 zepto.fragment 函数创建相应的 dom 节点
            Note: 在 Chrome 21 和 Firefox 15 以上版本内如果 fragment 不以 < 字符开始时
            会抛出代号为 12 的错误。关于 zepto.fragement 的详细信息在下方给出
        */
        if (selector[0] == '<' && fragmentRE.test(selector))
          // RegExp.$1 -> 返回第一个满足正则表达式的结果
          // selector = null -> 创建节点前选择器应置空
          dom = zepto.fragment(selector, RegExp.$1, context), selector = null
        // 如果存在 context, 优先创建具有 context 的 dom 节点, 并返回 find 函数结果
        else if (context !== undefined) return $(context).find(selector)
        // 如果为 CSS 选择器则将其传入 qsa 函数内查询相应 dom 元素
        else dom = zepto.qsa(document, selector)
      }
      // 如果 selector 类型为函数,则
      else if (isFunction(selector)) return $(document).ready(selector)
      // 如果 selector 参数为 Zepto 实例,则返回自身
      else if (zepto.isZ(selector)) return selector
      else {
        // 如果给定节点数组,则规范化数组
        if (isArray(selector)) dom = compact(selector)
        // 包裹 dom 节点
        else if (isObject(selector))
          dom = [selector], selector = null
        // fragmentRE = /^\s*<(\w+|!)[^>]*>/
        else if (fragmentRE.test(selector))
          // 如果存在 context, 则使用 zepto.fragment 函数优先创建含有 context 参数的 dom 节点
          dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
        // 不存在则返回 find 函数结果
        else if (context !== undefined) return $(context).find(selector)
        // 最后, 如果仅为 css 选择器,则调用 zepto.qsa 函数查询相应 dom 元素
        else dom = zepto.qsa(document, selector);
      }
      // 从 dom 集合中创建一个新的 Zepto collection 并在执行完后返回结果
      return zepto.Z(dom, selector)
    }

zepto.Z

zepto.Z = function(dom, selector) {
    // 返回 Z 实例,将 dom 节点和选择器作为此构造函数的参数
    return new 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];
    // 类数组对象在拥有操作数组的方法和 length 属性后会变成一个数组
    // 另外两个方法在 $.fn 对象内,此对象为 Zepto 的共享对象
    this.length = len;
    this.selector = selector || '';
}

$.fn

$.fn = {
    constructor: zepto.Z,
    length: 0,
    // emptyArray = []
    // 对返回的 Zepto 对象集合统一注入数组的方法,使其具有一般数组的功能
    forEach: emptyArray.forEach,
    reduce: emptyArray.reduce,
    push: emptyArray.push,
    sort: emptyArray.sort,
    splice: emptyArray.splice,
    indexOf: emptyArray.indexOf,
    ...,
    filter: function(selector){
        if (isFunction(selector)) return this.not(this.not(selector))
        return $(filter.call(this, function(element){
          return zepto.matches(element, selector)
        }))
      },
    find: function(selector){
        var result, $this = this
        if (!selector) result = $()
        else if (typeof selector == 'object')
          result = $(selector).filter(function(){
            var node = this
            return emptyArray.some.call($this, function(parent){
              return $.contains(parent, node)
            })
          })
        else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
        else result = this.map(function(){ return zepto.qsa(this, selector) })
        return result
      },
    ready: function(callback){
        // readyRE = /complete|loaded|interactive/
        // 满足条件后确定 dom 加载准备好后执行回调
        if (readyRE.test(document.readyState) && document.body) callback($)
        else document.addEventListener('DOMContentLoaded', function(){ callback($) }, 
             false)
        return this
      },
    attr: function(name, value){
        var result
        return (typeof name == 'string' && !(1 in arguments)) ?
          (0 in this && this[0].nodeType == 1 && (result = this[0].getAttribute(name)) !=                                                     null ? result : undefined) :
          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(nam                    e)))
          })
      },
    ...
}

zepto.fragment

zepto.fragment = function(html, name, properties) {
      var dom, nodes, container
      // 优化单标签参数
      if (singleTagRE.test(html)) dom = $(document.createElement(RegExp.$1));
      // 非单标签或 dom 未创建的情况
      if (!dom) {
        // tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)
        //                 (([\w:]+)[^>]*)\/>/ig
        if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
        // fragmentRE = /^\s*<(\w+|!)[^>]*>/
        if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
        /*
            containers = {
                  'tr': document.createElement('tbody'),
                  'tbody': table, 'thead': table, 'tfoot': table,
                  'td': tableRow, 'th': tableRow,
                  '*': document.createElement('div')
              },
        */
        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 为 Zepto 实例
        nodes = $(dom);
        // methodAttributes = ['val', 'css', 'html', 'text', 'data', 
        //                    'width', 'height', 'offset']
  
        $.each(properties, function(key, value) {
          // 查询是否存在当前 key,如果存在则调用 nodes 上属性为 key 的函数,将 value 值传入进去
          if (methodAttributes.indexOf(key) > -1) {
            nodes[key](value);
          }
          // 否则视为普通属性,调用 attr 函数设置属性
          else nodes.attr(key, value)
        })
      }

      return dom
}

zepto.qsa

zepto.qsa = function(element, selector){
      var found,
          maybeID = selector[0] == '#',
          maybeClass = !maybeID && selector[0] == '.',
          nameOnly = maybeID || maybeClass ? selector.slice(1) : selector,
          isSimple = simpleSelectorRE.test(nameOnly)
      // 确保选中[0]号位字符
      return (element.getElementById && isSimple && maybeID) ?
        // Safari DocumentFragment 没有 getElementById
        ( (found = element.getElementById(nameOnly)) ? [found] : [] ) :
        (element.nodeType !== 1 && element.nodeType !== 9 && element.nodeType !== 11) ? [] :
        slice.call(
          // DocumentFragment 没有 getElementsByClassName/TagName
          isSimple && !maybeID && element.getElementsByClassName ?
            maybeClass ? element.getElementsByClassName(nameOnly) :
            element.getElementsByTagName(selector) :
            element.querySelectorAll(selector)
        )
    }

工具类函数

isFunction

function isFunction(value) { return type(value) == "function" }

type

function type(obj) {
    return obj == null ? String(obj) :
    // 使用 toString 原型方法并调用 call 将 this 指向重置为传入的 obj 参数检查判断其类型是否为 object
    // class2type = {}
    class2type[toString.call(obj)] || "object"
}

isObject

function isObject(obj) { return type(obj) == "object" }

zepto.isZ

zepto.isZ = function(object) { return object instanceof zepto.Z }

isPlainObject

function isPlainObject(obj) {
   return isObject(obj) && !isWindow(obj) && Object.getPrototypeOf(obj) == Object.prototype
}

总结

基于以上代码,可以实现 $ / Zepto 函数本身的全部功能,部分函数实现可以查询 Zepto源代码

代码本身相对来说较为直观,难点更大程度上在于边界处理和类型校验,感兴趣的话可以更加深入研究

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值