Zepto 源码阅读 - 自己实现一个简化版

582 阅读2分钟

前言

最近看了下 zepto 的源码,阅读目的:

  • 让浮躁的心情归于平淡,远离焦虑
  • 学习组织代码的方式、代码结构
  • 巩固原型链知识
  • 给自己正反馈,激励前行

实现

我没有全部实现 zepto 的所有功能,只是实现简单的、比较核心的部分

因为是学习使用,参考 zepto 源码自行实现 zue 实现 zepto $ 函数功能(简化部分细节,主要是加深对 zepto 结构的了解)

实现 $ ,根据传递 selector 返回对应实例(支持 html 标签、ID、Class 选择器,支持传递 HTML 片段)

先看下主体结构

// 自执行函数,返回 Zue 即 $ 函数
const Zue = (function(){
  let $, Z, zue = {}
  
  Z = function(dom, selector) {
    
  }
  
  zue.Z = function(dom, selector) {
    return new Z(dom, selector)
  }
  
  // 根据 HTML 片段,创建 dom 数组
  zue.fragment = function(html) {
    
  }
  
  // 在 element 下面更具选择器查找对应 dom 元素,返回 dom 数组
  zue.qsa = function(element, selector)
  
  zue.init = function(selector) {
    let dom
    
    // 通过正则表达式判断是调用 zue.fragmeng 或 zue.qsa 来生成 dom 数组;
    
    return zue.Z(dom, selector)
  }
  
  // 入口函数,调用 zue.init() 返回 Z 对象实例
  $ = function(selector) {
    return zue.init()
  }
  
   // 通过类似方法,定义 $ 静态方法
  $.map = function(elements, cb) {
    // 具体实现
  }
  
   // 定义 Z 对象原型方法,所有 Z 实例共享
  $.fn = {
    // 定义各种方法例如 forEach、reduce 等等
  }
  
  // Z 原型指向 $.fn, 让 Z 实例共享 $.fn 上方法
  zue.Z.prototype = Z.prototype = $.fn
  
  return $;
})()
​
// 给 window.Zue 赋值,如果 window.$ 没有值,使用 $ 别名
window.Zue = Zue
window.$ === undefined && (window.$ = Zue)
​

以上就是 zepto 大概结构,解释一下代码

  • $(selector) 返回的是 Z 对象实例
  • Z.prototype 指向 .fn,.fn, 而 .fn 定义了常见的方法这个在 zepto 文档上可以看到
  • 本身也定义了一些帮助函数,例如类型检查,如:本身也定义了一些帮助函数,例如类型检查,如:.isEmptyObject $.isNumeric ,还有常见帮助函数
  • $ 是函数 Zepto 的别名
  • zepto.init 主要用来识别 selector ,判断调用 zue.qsa 或 zue.fragment 来返回 dom 数组
  • zue.qsa 和 zue.fragment 应该核心的实现部分,因为我是考虑简单的实现来理解大致结构,所以源码中简化了很多操作,实际的代码考虑很多,边界处理等等,感兴趣可以自己阅读
  • Z 构造函数,用来将 dom 数组复制到实例上,同时设置 selector 和 length 值

下面给下具体的实现

/**
 * $ 是函数,内部执行 zue.init 返回 Z 对象实例
 * zue.init 根据传入参数 selector ,调用 zue.qsa(dom 查找) 或者 zepro.fragment() 返回 dom 对象数组
 * 执行 zue.Z() 内容实用 new Z() 返回对象实例
 * Z 构造函数,初始化 len、selector、将 dom 数组绑定在对象实例上
 */// 自执行函数,返回 Zue 即 $ 函数const Zue = (function () {
  let $,
    Z,
    zue = {},
    fragmentRE = /^\s*<(\w+|!)[^>]*>/
​
  const isString = (str) =>
    Object.prototype.toString.call(str) === "[object String]"
​
  Z = function (dom, selector) {
    this.length = dom.length || 0
    dom.length &&
      dom.forEach((item, index) => {
        this[index] = item
      })
    this.selector = selector
  }
​
  zue.Z = function (dom, selector) {
    return new Z(dom, selector)
  }
​
  // 更具 HTML 片段,创建 dom 数组
  zue.fragment = function (html) {
    let dom
​
    // 创建 div 容器
    let container = document.createElement("div")
​
    // 将 HTML 片段赋值给 innerHTML
    container.innerHTML = "" + html
​
    // 遍历 container childNodes,返回对应 dom 数组
    dom = Array.prototype.slice.call(container.childNodes).map((item) => {
      return container.removeChild(item)
    })
​
    return dom
  }
​
  /**
   *
   * 只处理 class、id、html 标签选择器情况
   * @param element
   * @param selector
   */
  zue.qsa = function (element, selector) {
    return element.querySelectorAll(selector)
  }
​
  zue.init = function (selector, context) {
    let dom = []
​
    /**
     * selector 情况判定
     * 1. 为空
     * 2. String
     * */
​
    // 1. 为空
    if (!selector) return zue.Z()
    // 2. String
    else if (isString(selector)) {
      // 去前后空格
      selector = selector.trim()
      // 判断是否为合法 html fragment
      if (selector[0] === "<" && fragmentRE.test(selector)) {
        // 根据 html fragment 创建 dom
        dom = zue.fragment(selector)
        selector = null
      }
      // 否则认为是选择器
      else {
        dom = zue.qsa(document, selector)
      }
    }
​
    return zue.Z(dom, selector)
  }
​
  $ = function (selector, context) {
    return zue.init(selector, context)
  }
​
  // 定义 $ 静态方法
  $.map = function (elements, cb) {
    let arryElements = Array.from(elements)
    return arryElements.map(cb)
  }

  // 定义 Z 对象原型方法,所有 Z 实例共享
  $.fn = {
    constructor: zue.Z,
    forEach: [].forEach,
    reduce: [].reduce,
    push: [].push,
    sort: [].sort,
  }
​
  // Z 原型指向 $.fn, 让 Z 实例共享 $.fn 上方法
  zue.Z.prototype = Z.prototype = $.fn
​
  return $
})()
​
// 给 window.Zue 赋值,如果 window.$ 没有值,使用 $ 别名
window.Zue = Zue
window.$ === undefined && (window.$ = Zue)
​