zepto.js 源码解析

zepto.js 源码解析
Zepto是一个轻量级的针对现代高级浏览器的JavaScript库, 它与jquery有着类似的api。 如果你会用jquery,那么你也会用zepto。

Zepto 中文手册:https://www.runoob.com/manual/zeptojs.html

/* Zepto v1.0-1-ga3cab6c - polyfill zepto detect event ajax form fx - zeptojs.com/license */
;(function(undefined) {
  if (String.prototype.trim === undefined) // fix for iOS 3.2
  String.prototype.trim = function() {
    return this.replace(/^\s+|\s+$/g, '')
  }

  // For iOS 3.x
  // from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/reduce
  //这个方法的作用就是累似一个累计处理的作用,将前一条数据的处理结果用作下一次的处理
  //比如[1,2,3,4,].reduce(function(x,y){ return x+y}); ==> ((1+2)+3)+4,

  if (Array.prototype.reduce === undefined) Array.prototype.reduce = function(fun) {
    if (this === void 0 || this === null) throw new TypeError()
    var t = Object(this),
      len = t.length >>> 0,
      k = 0,
      accumulator
    if (typeof fun != 'function') throw new TypeError()
    if (len == 0 && arguments.length == 1) throw new TypeError()
    //取初始值  
    if (arguments.length >= 2) accumulator = arguments[1] //如果参数长度大于2个,则将第二个参数作为初始值
    else do {
      if (k in t) {
        accumulator = t[k++] //否则将数组的第一条数据作为初绍值
        break
      }
      if (++k >= len) throw new TypeError() //什么情况下会执行到这里来???
    } while (true)
    //遍历数组,将前一次的结果传入处理函数进行累计处理
    while (k < len) {
      if (k in t) accumulator = fun.call(undefined, accumulator, t[k], k, t)
      k++
    }
    return accumulator
  }

})()

var Zepto = (function() {
  var undefined, key, $, classList, emptyArray = [],
    slice = emptyArray.slice,
    filter = emptyArray.filter,
    document = window.document,
    elementDisplay = {}, classCache = {},
    getComputedStyle = document.defaultView.getComputedStyle,
    //设置CSS时,不用加px单位的属性
    cssNumber = {
      'column-count': 1,
      'columns': 1,
      'font-weight': 1,
      'line-height': 1,
      'opacity': 1,
      'z-index': 1,
      'zoom': 1
    },
    //HTML代码片断的正则
    fragmentRE = /^\s*<(\w+|!)[^>]*>/,
    //匹配非单独一个闭合标签的标签,类似将<div></div>写成了<div/>
    tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
    //根节点
    rootNodeRE = /^(?:body|html)$/i,

    //需要提供get和set的方法名
    // special attributes that should be get/set via method calls
    methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
    //相邻节点的一些操作
    adjacencyOperators = ['after', 'prepend', 'before', 'append'],
    table = document.createElement('table'),
    tableRow = document.createElement('tr'),
    //这里的用途是当需要给tr,tbody,thead,tfoot,td,th设置innerHTMl的时候,需要用其父元素作为容器来装载HTML字符串
    containers = {
      'tr': document.createElement('tbody'),
      'tbody': table,
      'thead': table,
      'tfoot': table,
      'td': tableRow,
      'th': tableRow,
      '*': document.createElement('div')
    },
    //当DOM ready的时候,document会有以下三种状态的一种
    readyRE = /complete|loaded|interactive/,
    //class选择器的正则
    classSelectorRE = /^\.([\w-]+)$/,
    //id选择器的正则
    idSelectorRE = /^#([\w-]*)$/,
    //DOM标签正则
    tagSelectorRE = /^[\w-]+$/,
    class2type = {},
    toString = class2type.toString,
    zepto = {},
    camelize, uniq,
    tempParent = document.createElement('div');

  //判断一个元素是否匹配给定的选择器
  zepto.matches = function(element, selector) {
    if (!element || element.nodeType !== 1) return false
    //引用浏览器提供的MatchesSelector方法
    var matchesSelector = element.webkitMatchesSelector || element.mozMatchesSelector || element.oMatchesSelector || element.matchesSelector
    if (matchesSelector) return matchesSelector.call(element, selector);
    //如果浏览器不支持MatchesSelector方法,则将节点放入一个临时div节点,
    //再通过selector来查找这个div下的节点集,再判断给定的element是否在节点集中,如果在,则返回一个非零(即非false)的数字
    // fall back to performing a selector:
    var match, parent = element.parentNode,temp = !parent
    //当element没有父节点,那么将其插入到一个临时的div里面
    if (temp)(parent = tempParent).appendChild(element)
    //将parent作为上下文,来查找selector的匹配结果,并获取element在结果集的索引,不存在时为-1,再通过~-1转成0,存在时返回一个非零的值
    match = ~zepto.qsa(parent, selector).indexOf(element)
    //将插入的节点删掉
    temp && tempParent.removeChild(element)
    return match
  }

  //获取对象类型 

  function type(obj) {
    //obj为null或者undefined时,直接返回'null'或'undefined'
    return obj == null ? String(obj) : class2type[toString.call(obj)] || "object"
  }

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

  function isWindow(obj) {
    return obj != null && obj == obj.window
  }

  function isDocument(obj) {
    return obj != null && obj.nodeType == obj.DOCUMENT_NODE
  }

  function isObject(obj) {
    return type(obj) == "object"
  }
  //对于通过字面量定义的对象和new Object的对象返回true,new Object时传参数的返回false
  //可参考http://snandy.iteye.com/blog/663245

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

  function isArray(value) {
    return value instanceof Array
  }
  //类数组,比如nodeList,这个只是做最简单的判断,如果给一个对象定义一个值为数据的length属性,它同样会返回true

  function likeArray(obj) {
    return typeof obj.length == 'number'
  }

  //清除给定的参数中的null或undefined,注意0==null,'' == null为false

  function compact(array) {
    return filter.call(array, function(item) {
      return item != null
    })
  }
  //类似得到一个数组的副本

  function flatten(array) {
    return array.length > 0 ? $.fn.concat.apply([], array) : array
  }
  //将字符串转成驼峰式的格式
  camelize = function(str) {
    return str.replace(/-+(.)?/g, function(match, chr) {
      return chr ? chr.toUpperCase() : ''
    })
  }
  //将字符串格式化成-拼接的形式,一般用在样式属性上,比如border-width

  function dasherize(str) {
    return str.replace(/::/g, '/') //将::替换成/
    .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') //在大小写字符之间插入_,大写在前,比如AAAbb,得到AA_Abb
    .replace(/([a-z\d])([A-Z])/g, '$1_$2') //在大小写字符之间插入_,小写或数字在前,比如bbbAaa,得到bbb_Aaa
    .replace(/_/g, '-') //将_替换成-
    .toLowerCase() //转成小写
  }
  //数组去重,如果该条数据在数组中的位置与循环的索引值不相同,则说明数组中有与其相同的值
  uniq = function(array) {
    return filter.call(array, function(item, idx) {
      return array.indexOf(item) == idx
    })
  }

  //将给定的参数生成正则

  function classRE(name) {
    //classCache,缓存正则
    return name in classCache ? classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'))
  }
  //给需要的样式值后面加上'px'单位,除了cssNumber里面的指定的那些

  function maybeAddPx(name, value) {
    return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value
  }
  //获取节点的默认display属性

  function defaultDisplay(nodeName) {
    var element, display
    if (!elementDisplay[nodeName]) { //缓存里不存在
      element = document.createElement(nodeName)
      document.body.appendChild(element)
      display = getComputedStyle(element, '').getPropertyValue("display")
      element.parentNode.removeChild(element)
      display == "none" && (display = "block") //当display等于none时,设置其值为block,搞不懂为毛要这样
      elementDisplay[nodeName] = display //缓存元素的默认display属性
    }
    return elementDisplay[nodeName]
  }
  //获取指定元素的子节点(不包含文本节点),Firefox不支持children,所以只能通过筛选childNodes

  function children(element) {
    return 'children' in element ? slice.call(element.children) : $.map(element.childNodes, function(node) {
      if (node.nodeType == 1) return node
    })
  }

  // `$.zepto.fragment` takes a html string and an optional tag name
  // to generate DOM nodes nodes from the given html string.
  // The generated DOM nodes are returned as an array.
  // This function can be overriden in plugins for example to make
  // it compatible with browsers that don't support the DOM fully.
  zepto.fragment = function(html, name, properties) {
    //将类似<div class="test"/>替换成<div class="test"></div>,算是一种修复吧
    if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
    //给name取标签名
    if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
    //设置容器标签名,如果不是tr,tbody,thead,tfoot,td,th,则容器标签名为div
    if (!(name in containers)) name = '*'

    var nodes, dom, container = containers[name] //创建容器
    container.innerHTML = '' + html //将html代码片断放入容器
    //取容器的子节点,这样就直接把字符串转成DOM节点了
    dom = $.each(slice.call(container.childNodes), function() {
      container.removeChild(this) //逐个删除
    })
    //如果properties是对象, 则将其当作属性来给添加进来的节点进行设置
    if (isPlainObject(properties)) {
      nodes = $(dom) //将dom转成zepto对象,为了方便下面调用zepto上的方法
      //遍历对象,设置属性
      $.each(properties, function(key, value) {
        //如果设置的是'val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset',则调用zepto上相对应的方法
        if (methodAttributes.indexOf(key) > -1) nodes[key](value)
        else nodes.attr(key, value)
      })
    }
    //返回将字符串转成的DOM节点后的数组,比如'<li></li><li></li><li></li>'转成[li,li,li]
    return dom
  }

  // `$.zepto.Z` swaps out the prototype of the given `dom` array
  // of nodes with `$.fn` and thus supplying all the Zepto functions
  // to the array. Note that `__proto__` is not supported on Internet
  // Explorer. This method can be overriden in plugins.
  zepto.Z = function(dom, selector) {
    dom = dom || []
    dom.__proto__ = $.fn //通过给dom设置__proto__属性指向$.fn来达到继承$.fn上所有方法的目的
    dom.selector = selector || ''
    return dom
  }

  // `$.zepto.isZ` should return `true` if the given object is a Zepto
  // collection. This method can be overriden in plugins.
  //判断给定的参数是否是Zepto集
  zepto.isZ = function(object) {
    return object instanceof zepto.Z
  }

  // `$.zepto.init` is Zepto's counterpart to jQuery's `$.fn.init` and
  // takes a CSS selector and an optional context (and handles various
  // special cases).
  // This method can be overriden in plugins.
  zepto.init = function(selector, context) {
    // If nothing given, return an empty Zepto collection
    if (!selector) return zepto.Z() //没有参数,返回空数组
    //如果selector是个函数,则在DOM ready的时候执行它
    else if (isFunction(selector)) return $(document).ready(selector)
    //如果selector是一个zepto.Z实例,则直接返回它自己
    else if (zepto.isZ(selector)) return selector
    else {
      var dom
      //如果selector是一个数组,则将其里面的null,undefined去掉
      if (isArray(selector)) dom = compact(selector)
      //如果selector是个对象,注意DOM节点的typeof值也是object,所以在里面还要再进行一次判断
      else if (isObject(selector))
      //如果是申明的对象,如{}, 则将selector属性copy到一个新对象,并将结果放入数组
      //如果是该对象是DOM,则直接放到数组中
      dom = [isPlainObject(selector) ? $.extend({}, selector) : selector], selector = null
      //如果selector是一段HTML代码片断,则将其转换成DOM节点
      else if (fragmentRE.test(selector)) dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
      //如果存在上下文context,则在上下文中查找selector,此时的selector为普通的CSS选择器
      else if (context !== undefined) return $(context).find(selector)
      //如果没有给定上下文,则在document中查找selector,此时的selector为普通的CSS选择器
      else dom = zepto.qsa(document, selector)
      //最后将查询结果转换成zepto集合
      return zepto.Z(dom, selector)
    }
  }

  // `$` will be the base `Zepto` object. When calling this
  // function just call `$.zepto.init, which makes the implementation
  // details of selecting nodes and creating Zepto collections
  // patchable in plugins.
  $ = function(selector, context) {
    return zepto.init(selector, context)
  }

  //扩展,deep表示是否深度扩展

  function extend(target, source, deep) {
    for (key in source)
    //如果深度扩展
    if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
      //如果要扩展的数据是对象且target相对应的key不是对象
      if (isPlainObject(source[key]) && !isPlainObject(target[key])) target[key] = {}
      //如果要扩展的数据是数组且target相对应的key不是数组
      if (isArray(source[key]) && !isArray(target[key])) target[key] = []
      extend(target[key], source[key], deep)
    } else if (source[key] !== undefined) target[key] = source[key]
  }

  // Copy all but undefined properties from one or more
  // objects to the `target` object.
  $.extend = function(target) {
    var deep, args = slice.call(arguments, 1)
    if (typeof target == 'boolean') { //当第一个参数为boolean类型的值时,表示是否深度扩展
      deep = target
      target = args.shift() //target取第二个参数
    }
    //遍历后面的参数,全部扩展到target上
    args.forEach(function(arg) {
      extend(target, arg, deep)
    })
    return target
  }

  // `$.zepto.qsa` is Zepto's CSS selector implementation which
  // uses `document.querySelectorAll` and optimizes for some special cases, like `#id`.
  // This method can be overriden in plugins.
  zepto.qsa = function(element, selector) {
    var found
    //当element为document,且selector为ID选择器时
    return (isDocument(element) && idSelectorRE.test(selector)) ?
    //直接返回document.getElementById,RegExp.$1为ID的值,当没有找节点时返回[]
    ((found = element.getElementById(RegExp.$1)) ? [found] : []) :
    //当element不为元素节点或者document时,返回[]
    (element.nodeType !== 1 && element.nodeType !== 9) ? [] :
    //否则将获取到的结果转成数组并返回
    slice.call(
    //如果selector是标签名,直接调用getElementsByClassName
    classSelectorRE.test(selector) ? element.getElementsByClassName(RegExp.$1) :
    //如果selector是标签名,直接调用getElementsByTagName
    tagSelectorRE.test(selector) ? element.getElementsByTagName(selector) :
    //否则调用querySelectorAll
    element.querySelectorAll(selector))
  }

  //在结果中进行过滤

  function filtered(nodes, selector) {
    return selector === undefined ? $(nodes) : $(nodes).filter(selector)
  }
  //判断parent是否包含node
  $.contains = function(parent, node) {
    return parent !== node && parent.contains(node)
  }

  //这个函数在整个库中取着很得要的作用,处理arg为函数或者值的情况
  //下面很多设置元素属性时的函数都有用到

  function funcArg(context, arg, idx, payload) {
    return isFunction(arg) ? arg.call(context, idx, payload) : arg
  }

  function setAttribute(node, name, value) {
    //如果设置的值为null或undefined,则相当于删除该属性,否则设置name属性为value
    value == null ? node.removeAttribute(name) : node.setAttribute(name, value)
  }

  // access className property while respecting SVGAnimatedString

  function className(node, value) {
    var klass = node.className,
      svg = klass && klass.baseVal !== undefined

    if (value === undefined) return svg ? klass.baseVal : klass
    svg ? (klass.baseVal = value) : (node.className = value)
  }

  // "true"  => true
  // "false" => false
  // "null"  => null
  // "42"    => 42
  // "42.5"  => 42.5
  // JSON    => parse if valid
  // String  => self

  function deserializeValue(value) {
    var num
    try {
      return value ? value == "true" || (value == "false" ? false : value == "null" ? null : !isNaN(num = Number(value)) ? num : /^[\[\{]/.test(value) ? $.parseJSON(value) : value) : value
    } catch (e) {
      return value
    }
  }

  $.type = type
  $.isFunction = isFunction
  $.isWindow = isWindow
  $.isArray = isArray
  $.isPlainObject = isPlainObject

  //空对象
  $.isEmptyObject = function(obj) {
    var name
    for (name in obj) return false
    return true
  }

  //获取指定的值在数组中的位置
  $.inArray = function(elem, array, i) {
    return emptyArray.indexOf.call(array, elem, i)
  }
  //将字符串转成驼峰式的格式
  $.camelCase = camelize
  //去字符串头尾空格
  $.trim = function(str) {
    return str.trim()
  }

  // plugin compatibility
  $.uuid = 0
  $.support = {}
  $.expr = {}

  //遍历elements,将每条记录放入callback里进宪处理,保存处理函数返回值不为null或undefined的结果
  //注意这里没有统一的用for in,是为了避免遍历数据默认属性的情况,如数组的toString,valueOf
  $.map = function(elements, callback) {
    var value, values = [],
      i, key
      //如果被遍历的数据是数组或者nodeList
    if (likeArray(elements)) for (i = 0; i < elements.length; i++) {
      value = callback(elements[i], i)
      if (value != null) values.push(value)
    } else
    //如果是对象
    for (key in elements) {
      value = callback(elements[key], key)
      if (value != null) values.push(value)
    }
    return flatten(values)
  }

  //遍历数组,将每条数据作为callback的上下文,并传入数据以及数据的索引进行处理,如果其中一条数据的处理结果明确返回false,
  //则停止遍历,并返回elements
  $.each = function(elements, callback) {
    var i, key
    if (likeArray(elements)) {
      for (i = 0; i < elements.length; i++)
      if (callback.call(elements[i], i, elements[i]) === false) return elements
    } else {
      for (key in elements)
      if (callback.call(elements[key], key, elements[key]) === false) return elements
    }

    return elements
  }
  //过滤
  $.grep = function(elements, callback) {
    return filter.call(elements, callback)
  }

  if (window.JSON) $.parseJSON = JSON.parse

  // Populate the class2type map
  //填充class2type的值

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值