zepto js 源碼 解讀

/* 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的值
  $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
    class2type["[object " + name + "]"] = name.toLowerCase()
  })

  //針對DOM的一些操做
  // Define methods that will be available on all
  // Zepto collections
  $.fn = {
    // Because a collection acts like an array
    // copy over these useful array functions.
    forEach: emptyArray.forEach,
    reduce: emptyArray.reduce,
    push: emptyArray.push,
    sort: emptyArray.sort,
    indexOf: emptyArray.indexOf,
    concat: emptyArray.concat,

    // `map` and `slice` in the jQuery API work differently
    // from their array counterparts
    map: function(fn) {
      return $($.map(this, function(el, i) {
        return fn.call(el, i, el)
      }))
    },
    slice: function() {
      return $(slice.apply(this, arguments))
    },
    //DOM Ready
    ready: function(callback) {
      if (readyRE.test(document.readyState)) callback($)
      else document.addEventListener('DOMContentLoaded', function() {
        callback($)
      }, false)
      return this
    },
    //取集合中對應指定索引的值,若是idx小於0,則idx等於idx+length,length爲集合的長度
    get: function(idx) {
      return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length]
    },
    //將集合轉換爲數組
    toArray: function() {
      return this.get()
    },
    //獲取集合長度
    size: function() {
      return this.length
    },
    //將集合從dom中刪除
    remove: function() {
      return this.each(function() {
        if (this.parentNode != null) this.parentNode.removeChild(this)
      })
    },
    //遍歷集合,將集合中的每一項放入callback中進行處理,去掉結果爲false的項,注意這裏的callback若是明確返回false
    //那麼就會中止循環了
    each: function(callback) {
      emptyArray.every.call(this, function(el, idx) {
        return callback.call(el, idx, el) !== false
      })
      return this
    },
    //過濾集合,返回處理結果爲true的記錄
    filter: function(selector) {
      //this.not(selector)取到須要排除的集合,第二次再取反(這個時候this.not的參數就是一個集合了),獲得想要的集合
      if (isFunction(selector)) return this.not(this.not(selector))
      //filter收集返回結果爲true的記錄
      return $(filter.call(this, function(element) {
        return zepto.matches(element, selector) //當element與selector匹配,則收集
      }))
    },
    //將由selector獲取到的結果追加到當前集合中
    add: function(selector, context) {
      return $(uniq(this.concat($(selector, context)))) //追加並去重
    },
    //返回集合中的第1條記錄是否與selector匹配
    is: function(selector) {
      return this.length > 0 && zepto.matches(this[0], selector)
    },
    //排除集合裏知足條件的記錄,接收參數爲:css選擇器,function, dom ,nodeList
    not: function(selector) {
      var nodes = []
      //當selector爲函數時,safari下的typeof odeList也是function,因此這裏須要再加一個判斷selector.call !== undefined
      if (isFunction(selector) && selector.call !== undefined) {
        this.each(function(idx) {
          //注意這裏收集的是selector.call(this,idx)返回結果爲false的時候記錄
          if (!selector.call(this, idx)) nodes.push(this)
        })
      } else {
        //當selector爲字符串的時候,對集合進行篩選,也就是篩選出集合中知足selector的記錄
        var excludes = typeof selector == 'string' ? this.filter(selector) :
        //當selector爲nodeList時執行slice.call(selector),注意這裏的isFunction(selector.item)是爲了排除selector爲數組的狀況
        //當selector爲css選擇器,執行$(selector)
        (likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
        this.forEach(function(el) {
          //篩選出不在excludes集合裏的記錄,達到排除的目的
          if (excludes.indexOf(el) < 0) nodes.push(el)
        })
      }
      return $(nodes) //因爲上面獲得的結果是數組,這裏須要轉成zepto對象,以便繼承其它方法,實現鏈寫
    },
    /*
        接收node和string做爲參數,給當前集合篩選出包含selector的集合
        isObject(selector)是判斷參數是不是node,由於typeof node == 'object'
        當參數爲node時,只須要判讀當前記當裏是否包含node節點便可
        當參數爲string時,則在當前記錄裏查詢selector,若是長度爲0,則爲false,filter函數就會過濾掉這條記錄,不然保存該記錄
    */
    has: function(selector) {
      return this.filter(function() {
        return isObject(selector) ? $.contains(this, selector) : $(this).find(selector).size()
      })
    },
    /* 
        選擇集合中指定索引的記錄,當idx爲-1時,取最後一個記錄
    */
    eq: function(idx) {
      return idx === -1 ? this.slice(idx) : this.slice(idx, +idx + 1)
    },
    /* 
        取集合中的第一條記錄
    */
    first: function() {
      var el = this[0] //取集合中的第一條記錄
      //若是集合中的第一條數據自己就已是zepto對象則直接返回自己,不然轉成zepto對象
      //el && !isObject(el)在這裏取到一個判斷el是否爲節點的狀況,由於若是el是節點,那麼isObject(el)的結果就是true
      return el && !isObject(el) ? el : $(el)
    },
    /* 
        取集合中的最後一條記錄
    */
    last: function() {
      var el = this[this.length - 1] //取集合中的最後一條記錄
      //若是el爲node,則isObject(el)會爲true,須要轉成zepto對象
      return el && !isObject(el) ? el : $(el)
    },
    /* 
        在當前集合中查找selector,selector能夠是集合,選擇器,以及節點
    */
    find: function(selector) {
      var result, $this = this
      //若是selector爲node或者zepto集合時
      if (typeof selector == 'object')
      //遍歷selector,篩選出父級爲集合中記錄的selector
      result = $(selector).filter(function() {
        var node = this
        //若是$.contains(parent, node)返回true,則emptyArray.some也會返回true,外層的filter則會收錄該條記錄
        return emptyArray.some.call($this, function(parent) {
          return $.contains(parent, node)
        })
      })
      //若是selector是css選擇器
      //若是當前集合長度爲1時,調用zepto.qsa,將結果轉成zepto對象
      else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
      //若是長度大於1,則調用map遍歷
      else result = this.map(function() {
        return zepto.qsa(this, selector)
      })
      return result
    },
    //取集合中第一記錄的最近的知足條件的父級元素
    closest: function(selector, context) {
      var node = this[0],
        collection = false
      if (typeof selector == 'object') collection = $(selector)
      //當selector是node或者zepto集合時,若是node不在collection集合中時須要取node.parentNode進行判斷
      //當selector是字符串選擇器時,若是node與selector不匹配,則須要取node.parentNode進行判斷
      while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
      //當node 不是context,document的時候,取node.parentNode
      node = node !== context && !isDocument(node) && node.parentNode
      return $(node)
    },
    //取集合全部父級元素
    parents: function(selector) {
      var ancestors = [],
        nodes = this
        //經過遍歷nodes獲得全部父級,注意在while裏nodes被從新賦值了
        //本函數的巧妙之處在於,不停在獲取父級,再遍歷父級獲取父級的父級
        //而後再經過去重,獲得最終想要的結果,當到達最頂層的父級時,nodes.length就爲0了
      while (nodes.length > 0)
      //nodes被從新賦值爲收集到的父級集合
      nodes = $.map(nodes, function(node) {
        //遍歷nodes,收集集合的第一層父級
        //ancestors.indexOf(node) < 0用來去重複
        if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) {
          ancestors.push(node) //收集已經獲取到的父級元素,用於去重複
          return node
        }
      })
      //上面還只是取到了全部的父級元素,這裏還須要對其進行篩選從而獲得最終想要的結果
      return filtered(ancestors, selector)
    },
    //獲取集合的父節點
    parent: function(selector) {
      return filtered(uniq(this.pluck('parentNode')), selector)
    },
    children: function(selector) {
      return filtered(this.map(function() {
        return children(this)
      }), selector)
    },
    contents: function() {
      return this.map(function() {
        return slice.call(this.childNodes)
      })
    },
    siblings: function(selector) {
      return filtered(this.map(function(i, el) {
        //先獲取該節點的父節點中的全部子節點,再排除自己
        return filter.call(children(el.parentNode), function(child) {
          return child !== el
        })
      }), selector)
    },
    empty: function() {
      return this.each(function() {
        this.innerHTML = ''
      })
    },
    //根據屬性來獲取當前集合的相關集合
    pluck: function(property) {
      return $.map(this, function(el) {
        return el[property]
      })
    },
    show: function() {
      return this.each(function() {
        //清除元素的內聯display="none"的樣式
        this.style.display == "none" && (this.style.display = null)
        //當樣式表裏的該元素的display樣式爲none時,設置它的display爲默認值
        if (getComputedStyle(this, '').getPropertyValue("display") == "none") this.style.display = defaultDisplay(this.nodeName) //defaultDisplay是獲取元素默認display的方法
      })
    },
    replaceWith: function(newContent) {
      //將要替換的內容插入到被替換的內容前面,而後刪除被替換的內容
      return this.before(newContent).remove()
    },
    wrap: function(structure) {
      var func = isFunction(structure)
      if (this[0] && !func)
      //若是structure是字符串,則將其轉成DOM
      var dom = $(structure).get(0),
        //若是structure是已經存在於頁面上的節點或者被wrap的記錄不僅一條,則須要clone dom
        clone = dom.parentNode || this.length > 1

      return this.each(function(index) {
        $(this).wrapAll(
        func ? structure.call(this, index) : clone ? dom.cloneNode(true) : dom)
      })
    },
    wrapAll: function(structure) {
      if (this[0]) {
        //將要包裹的內容插入到第一條記錄的前面,算是給structure定位圍置
        $(this[0]).before(structure = $(structure))
        var children
        // drill down to the inmost element
        //取structure裏的第一個子節點的最裏層
        while ((children = structure.children()).length) structure = children.first()
        //將當前集合插入到最裏層的節點裏,達到wrapAll的目的
        $(structure).append(this)
      }
      return this
    },
    //在匹配元素裏的內容外包一層結構
    wrapInner: function(structure) {
      var func = isFunction(structure)
      return this.each(function(index) {
        //原理就是獲取節點的內容,而後用structure將內容包起來,若是內容不存在,則直接將structure append到該節點
        var self = $(this),
          contents = self.contents(),
          dom = func ? structure.call(this, index) : structure
          contents.length ? contents.wrapAll(dom) : self.append(dom)
      })
    },
    unwrap: function() {
      //用子元素替換掉父級
      this.parent().each(function() {
        $(this).replaceWith($(this).children())
      })
      return this
    },
    //clone node
    clone: function() {
      return this.map(function() {
        return this.cloneNode(true)
      })
    },
    //隱藏集合
    hide: function() {
      return this.css("display", "none")
    },
    toggle: function(setting) {
      return this.each(function() {
        var el = $(this);
        /* 
            這個setting取得做用就是控制顯示與隱藏,並不切換,當它的值爲true時,一直顯示,false時,一直隱藏
            這個地方的判斷看上去有點繞,其實也簡單,意思是說,當不給toogle參數時,根據元素的display是否等於none來決定顯示或者隱藏元素
            當給toogle參數,就沒有切換效果了,只是簡單的根據參數值來決定顯示或隱藏。若是參數true,至關於show方法,false則至關於hide方法
        */
        (setting === undefined ? el.css("display") == "none" : setting) ? el.show() : el.hide()
      })
    },
    prev: function(selector) {
      return $(this.pluck('previousElementSibling')).filter(selector || '*')
    },
    next: function(selector) {
      return $(this.pluck('nextElementSibling')).filter(selector || '*')
    },
    //當有參數時,設置集合每條記錄的HTML,沒有參數時,則爲獲取集合第一條記錄的HTML,若是集合的長度爲0,則返回null
    html: function(html) {
      return html === undefined ?
      //參數html不存在時,獲取集合中第一條記錄的html
      (this.length > 0 ? this[0].innerHTML : null) :
      //不然遍歷集合,設置每條記錄的html
      this.each(function(idx) {
        //記錄原始的innerHTMl
        var originHtml = this.innerHTML
        //若是參數html是字符串直接插入到記錄中,
        //若是是函數,則將當前記錄做爲上下文,調用該函數,且傳入該記錄的索引和原始innerHTML做爲參數
        $(this).empty().append(funcArg(this, html, idx, originHtml))
      })
    },
    text: function(text) {
      //若是不給定text參數,則爲獲取功能,集合長度大於0時,取第一條數據的textContent,不然返回null,
      //若是給定text參數,則爲集合的每一條數據設置textContent爲text
      return text === undefined ? (this.length > 0 ? this[0].textContent : null) : this.each(function() {
        this.textContent = text
      })
    },
    attr: function(name, value) {
      var result
      //當只有name且爲字符串時,表示獲取第一條記錄的屬性
      return (typeof name == 'string' && value === undefined) ?
      //集合沒有記錄或者集合的元素不是node類型,返回undefined
      (this.length == 0 || this[0].nodeType !== 1 ? undefined :
      //若是取的是input的value
      (name == 'value' && this[0].nodeName == 'INPUT') ? this.val() :
      //注意直接定義在node上的屬性,在標準瀏覽器和ie9,10中用getAttribute取不到,獲得的結果是null
      //好比div.aa = 10,用div.getAttribute('aa')獲得的是null,須要用div.aa或者div['aa']這樣來取
      (!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result) :
      this.each(function(idx) {
        if (this.nodeType !== 1) return
        //若是name是一個對象,如{'id':'test','value':11},則給數據設置屬性
        if (isObject(name)) for (key in name) setAttribute(this, key, name[key])
        //若是name只是一個普通的屬性字符串,用funcArg來處理value是值或者function的狀況最終返回一個屬性值
        //若是funcArg函數返回的是undefined或者null,則至關於刪除元素的屬性
        else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)))
      })
    },
    removeAttr: function(name) {
      return this.each(function() {
        this.nodeType === 1 && setAttribute(this, name)//setAttribute的第三個參數爲null時,效果是刪除name屬性
      })
    },
    //獲取第一條數據的指定的name屬性或者給每條數據添加自定義屬性,注意和setAttribute的區別
    prop: function(name, value) {
      //沒有給定value時,爲獲取,給定value則給每一條數據添加,value能夠爲值也能夠是一個返回值的函數
      return (value === undefined) ? (this[0] && this[0][name]) : this.each(function(idx) {
        this[name] = funcArg(this, value, idx, this[name])
      })
    },
    data: function(name, value) {
      //經過調用attr方法來實現獲取與設置的效果,注意attr方法裏,當value存在的時候,返回的是集合自己,若是不存在,則是返回獲取的值
      var data = this.attr('data-' + dasherize(name), value)
      return data !== null ? deserializeValue(data) : undefined
    },
    val: function(value) {
      return (value === undefined) ?
      //若是是多選的select,則返回一個包含被選中的option的值的數組
      (this[0] && (this[0].multiple ? $(this[0]).find('option').filter(function(o) {
        return this.selected
      }).pluck('value') : this[0].value)) : this.each(function(idx) {
        this.value = funcArg(this, value, idx, this.value)
      })
    },
    offset: function(coordinates) {
      if (coordinates) return this.each(function(index) {
        var $this = $(this),
          //coordinates爲{}時直接返回,爲函數時返回處理結果給coords
          coords = funcArg(this, coordinates, index, $this.offset()),
          //取父級的offset
          parentOffset = $this.offsetParent().offset(),
          //計算出它們之間的差,得出其偏移量  
          props = {
            top: coords.top - parentOffset.top,
            left: coords.left - parentOffset.left
          }
          //注意元素的position爲static時,設置top,left是無效的
        if ($this.css('position') == 'static') props['position'] = 'relative'
        $this.css(props)
      })
      //取第一條記錄的offset,包括offsetTop,offsetLeft,offsetWidth,offsetHeight
      if (this.length == 0) return null
      var obj = this[0].getBoundingClientRect()
      //window.pageYOffset就是相似Math.max(document.documentElement.scrollTop||document.body.scrollTop)
      return {
        left: obj.left + window.pageXOffset,
        top: obj.top + window.pageYOffset,
        width: Math.round(obj.width),
        height: Math.round(obj.height)
      }
    },
    css: function(property, value) {
      //獲取指定的樣式
      if (arguments.length < 2 && typeof property == 'string') return this[0] && (this[0].style[camelize(property)] || getComputedStyle(this[0], '').getPropertyValue(property))
      //設置樣式
      var css = ''
      if (type(property) == 'string') {
        if (!value && value !== 0) //當value的值爲非零的能夠轉成false的值時如(null,undefined),刪掉property樣式
        this.each(function() {
          //style.removeProperty 移除指定的CSS樣式名(IE不支持DOM的style方法)
          this.style.removeProperty(dasherize(property))
        })
        else css = dasherize(property) + ":" + maybeAddPx(property, value)
      } else {
        //當property是對象時
        for (key in property)
        if (!property[key] && property[key] !== 0)
        //當property[key]的值爲非零的能夠轉成false的值時,刪掉key樣式
        this.each(function() {
          this.style.removeProperty(dasherize(key))
        })
        else css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';'
      }
      //設置
      return this.each(function() {
        this.style.cssText += ';' + css
      })
    },
    index: function(element) {
      //這裏的$(element)[0]是爲了將字符串轉成node,由於this是個包含node的數組
      //當不指定element時,取集合中第一條記錄在其父節點的位置
      //this.parent().children().indexOf(this[0])這句很巧妙,和取第一記錄的parent().children().indexOf(this)相同
      return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
    },
    hasClass: function(name) {
      return emptyArray.some.call(this, function(el) {
        //注意這裏的this是classRE(name)生成的正則
        return this.test(className(el))
      }, classRE(name))
    },
    addClass: function(name) {
      return this.each(function(idx) {
        classList = []
        var cls = className(this),
          newName = funcArg(this, name, idx, cls)
          //處理同時多個類的狀況,用空格分開
          newName.split(/\s+/g).forEach(function(klass) {
            if (!$(this).hasClass(klass)) classList.push(klass)
          }, this)
          classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "))
      })
    },
    removeClass: function(name) {
      return this.each(function(idx) {
        if (name === undefined) return className(this, '')
        classList = className(this)
        funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass) {
          classList = classList.replace(classRE(klass), " ")
        })
        className(this, classList.trim())
      })
    },
    toggleClass: function(name, when) {
      return this.each(function(idx) {
        var $this = $(this),
          names = funcArg(this, name, idx, className(this))
          names.split(/\s+/g).forEach(function(klass) {
            (when === undefined ? !$this.hasClass(klass) : when) ? $this.addClass(klass) : $this.removeClass(klass)
          })
      })
    },
    scrollTop: function() {
      if (!this.length) return
      return ('scrollTop' in this[0]) ? this[0].scrollTop : this[0].scrollY
    },
    position: function() {
      if (!this.length) return

      var elem = this[0],
        // Get *real* offsetParent
        offsetParent = this.offsetParent(),
        // Get correct offsets
        offset = this.offset(),
        parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? {
          top: 0,
          left: 0
        } : offsetParent.offset()

        // Subtract element margins
        // note: when an element has margin: auto the offsetLeft and marginLeft
        // are the same in Safari causing offset.left to incorrectly be 0
        offset.top -= parseFloat($(elem).css('margin-top')) || 0
        offset.left -= parseFloat($(elem).css('margin-left')) || 0

        // Add offsetParent borders
        parentOffset.top += parseFloat($(offsetParent[0]).css('border-top-width')) || 0
        parentOffset.left += parseFloat($(offsetParent[0]).css('border-left-width')) || 0

        // Subtract the two offsets
      return {
        top: offset.top - parentOffset.top,
        left: offset.left - parentOffset.left
      }
    },
    offsetParent: function() {
      return this.map(function() {
        var parent = this.offsetParent || document.body
        while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static")
        parent = parent.offsetParent
        return parent
      })
    }
  }

  // for now
  $.fn.detach = $.fn.remove

  // Generate the `width` and `height` functions
  ;
  ['width', 'height'].forEach(function(dimension) {
    $.fn[dimension] = function(value) {
      var offset, el = this[0],
        //將width,hegiht轉成Width,Height,用於取window或者document的width和height
        Dimension = dimension.replace(/./, function(m) {
          return m[0].toUpperCase()
        })
        //沒有參數爲獲取,獲取window的width和height用innerWidth,innerHeight
      if (value === undefined) return isWindow(el) ? el['inner' + Dimension] :
      //獲取document的width和height時,用offsetWidth,offsetHeight
      isDocument(el) ? el.documentElement['offset' + Dimension] : (offset = this.offset()) && offset[dimension]
      else return this.each(function(idx) {
        el = $(this)
        el.css(dimension, funcArg(this, value, idx, el[dimension]()))
      })
    }
  })

  function traverseNode(node, fun) {
    fun(node)
    for (var key in node.childNodes) traverseNode(node.childNodes[key], fun)
  }

  // Generate the `after`, `prepend`, `before`, `append`,
  // `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods.
  adjacencyOperators.forEach(function(operator, operatorIndex) {
    var inside = operatorIndex % 2 //=> prepend, append

    $.fn[operator] = function() {
      // arguments can be nodes, arrays of nodes, Zepto objects and HTML strings
      var argType, nodes = $.map(arguments, function(arg) {
        argType = type(arg)
        return argType == "object" || argType == "array" || arg == null ? arg : zepto.fragment(arg)
      }),
        parent, copyByClone = this.length > 1 //若是集合的長度大於集,則須要clone被插入的節點
      if (nodes.length < 1) return this

      return this.each(function(_, target) {
        parent = inside ? target : target.parentNode

        //經過改變target將after,prepend,append操做轉成before操做,insertBefore的第二個參數爲null時等於appendChild操做
        target = operatorIndex == 0 ? target.nextSibling : operatorIndex == 1 ? target.firstChild : operatorIndex == 2 ? target : null

        nodes.forEach(function(node) {
          if (copyByClone) node = node.cloneNode(true)
          else if (!parent) return $(node).remove()

          //插入節點後,若是被插入的節點是SCRIPT,則執行裏面的內容並將window設爲上下文
          traverseNode(parent.insertBefore(node, target), function(el) {
            if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' && (!el.type || el.type === 'text/javascript') && !el.src) window['eval'].call(window, el.innerHTML)
          })
        })
      })
    }

    // after    => insertAfter
    // prepend  => prependTo
    // before   => insertBefore
    // append   => appendTo
    $.fn[inside ? operator + 'To' : 'insert' + (operatorIndex ? 'Before' : 'After')] = function(html) {
      $(html)[operator](this)
      return this
    }
  })

  zepto.Z.prototype = $.fn

  // Export internal API functions in the `$.zepto` namespace
  zepto.uniq = uniq
  zepto.deserializeValue = deserializeValue
  $.zepto = zepto

  return $
})();

window.Zepto = Zepto;
'$' in window || (window.$ = Zepto);

;(function($) {
  function detect(ua) {
    var os = this.os = {}, browser = this.browser = {},
    webkit = ua.match(/WebKit\/([\d.]+)/),
      android = ua.match(/(Android)\s+([\d.]+)/),
      ipad = ua.match(/(iPad).*OS\s([\d_]+)/),
      iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/),
      webos = ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),
      touchpad = webos && ua.match(/TouchPad/),
      kindle = ua.match(/Kindle\/([\d.]+)/),
      silk = ua.match(/Silk\/([\d._]+)/),
      blackberry = ua.match(/(BlackBerry).*Version\/([\d.]+)/),
      bb10 = ua.match(/(BB10).*Version\/([\d.]+)/),
      rimtabletos = ua.match(/(RIM\sTablet\sOS)\s([\d.]+)/),
      playbook = ua.match(/PlayBook/),
      chrome = ua.match(/Chrome\/([\d.]+)/) || ua.match(/CriOS\/([\d.]+)/),
      firefox = ua.match(/Firefox\/([\d.]+)/)

      // Todo: clean this up with a better OS/browser seperation:
      // - discern (more) between multiple browsers on android
      // - decide if kindle fire in silk mode is android or not
      // - Firefox on Android doesn't specify the Android version
      // - possibly devide in os, device and browser hashes

    if (browser.webkit = !! webkit) browser.version = webkit[1]

    if (android) os.android = true, os.version = android[2]
    if (iphone) os.ios = os.iphone = true, os.version = iphone[2].replace(/_/g, '.')
    if (ipad) os.ios = os.ipad = true, os.version = ipad[2].replace(/_/g, '.')
    if (webos) os.webos = true, os.version = webos[2]
    if (touchpad) os.touchpad = true
    if (blackberry) os.blackberry = true, os.version = blackberry[2]
    if (bb10) os.bb10 = true, os.version = bb10[2]
    if (rimtabletos) os.rimtabletos = true, os.version = rimtabletos[2]
    if (playbook) browser.playbook = true
    if (kindle) os.kindle = true, os.version = kindle[1]
    if (silk) browser.silk = true, browser.version = silk[1]
    if (!silk && os.android && ua.match(/Kindle Fire/)) browser.silk = true
    if (chrome) browser.chrome = true, browser.version = chrome[1]
    if (firefox) browser.firefox = true, browser.version = firefox[1]

    os.tablet = !! (ipad || playbook || (android && !ua.match(/Mobile/)) || (firefox && ua.match(/Tablet/)))
    os.phone = !! (!os.tablet && (android || iphone || webos || blackberry || bb10 || (chrome && ua.match(/Android/)) || (chrome && ua.match(/CriOS\/([\d.]+)/)) || (firefox && ua.match(/Mobile/))))
  }

  detect.call($, navigator.userAgent)
  // make available to unit tests
  $.__detect = detect

})(Zepto)

/* 
事件處理部份
 */
;
(function($) {
  var $$ = $.zepto.qsa,
    handlers = {}, _zid = 1,
    specialEvents = {},
    hover = {
      mouseenter: 'mouseover',
      mouseleave: 'mouseout'
    }

  specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'

  //取element的惟一標示符,若是沒有,則設置一個並返回

  function zid(element) {
    return element._zid || (element._zid = _zid++)
  }
  //查找綁定在元素上的指定類型的事件處理函數集合

  function findHandlers(element, event, fn, selector) {
    event = parse(event)
    if (event.ns) var matcher = matcherFor(event.ns)
    return (handlers[zid(element)] || []).filter(function(handler) {
      return handler && (!event.e || handler.e == event.e) //判斷事件類型是否相同
      &&
      (!event.ns || matcher.test(handler.ns)) //判斷事件命名空間是否相同
      //注意函數是引用類型的數據zid(handler.fn)的做用是返回handler.fn的標示符,若是沒有,則給它添加一個,
      //這樣若是fn和handler.fn引用的是同一個函數,那麼fn上應該也可相同的標示符,
      //這裏就是經過這一點來判斷兩個變量是否引用的同一個函數
      &&
      (!fn || zid(handler.fn) === zid(fn)) && (!selector || handler.sel == selector)
    })
  }
  //解析事件類型,返回一個包含事件名稱和事件命名空間的對象

  function parse(event) {
    var parts = ('' + event).split('.')
    return {
      e: parts[0],
      ns: parts.slice(1).sort().join(' ')
    }
  }
  //生成命名空間的正則

  function matcherFor(ns) {
    return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
  }
  //遍歷events

  function eachEvent(events, fn, iterator) {
    if ($.type(events) != "string") $.each(events, iterator)
    else events.split(/\s/).forEach(function(type) {
      iterator(type, fn)
    })
  }
  //經過給focus和blur事件設置爲捕獲來達到事件冒泡的目的

  function eventCapture(handler, captureSetting) {
    return handler.del && (handler.e == 'focus' || handler.e == 'blur') || !! captureSetting
  }

  //修復不支持mouseenter和mouseleave的狀況

  function realEvent(type) {
    return hover[type] || type
  }

  //給元素綁定監聽事件,可同時綁定多個事件類型,如['click','mouseover','mouseout'],也能夠是'click mouseover mouseout'

  function add(element, events, fn, selector, getDelegate, capture) {
    var id = zid(element),
      set = (handlers[id] || (handlers[id] = [])) //元素上已經綁定的全部事件處理函數
      eachEvent(events, fn, function(event, fn) {
        var handler = parse(event)
        //保存fn,下面爲了處理mouseenter, mouseleave時,對fn進行了修改
        handler.fn = fn
        handler.sel = selector
        // 模仿 mouseenter, mouseleave
        if (handler.e in hover) fn = function(e) {
          /* 
            relatedTarget爲事件相關對象,只有在mouseover和mouseout事件時纔有值
            mouseover時表示的是鼠標移出的那個對象,mouseout時表示的是鼠標移入的那個對象
            當related不存在,表示事件不是mouseover或者mouseout,mouseover時!$.contains(this, related)當相關對象不在事件對象內
            且related !== this相關對象不是事件對象時,表示鼠標已經從事件對象外部移入到了對象自己,這個時間是要執行處理函數的
            當鼠標從事件對象上移入到子節點的時候related就等於this了,且!$.contains(this, related)也不成立,這個時間是不須要執行處理函數的
        */
          var related = e.relatedTarget
          if (!related || (related !== this && !$.contains(this, related))) return handler.fn.apply(this, arguments)
        }
        //事件委託
        handler.del = getDelegate && getDelegate(fn, event)
        var callback = handler.del || fn
        handler.proxy = function(e) {
          var result = callback.apply(element, [e].concat(e.data))
          //當事件處理函數返回false時,阻止默認操做和冒泡
          if (result === false) e.preventDefault(), e.stopPropagation()
          return result
        }
        //設置處理函數的在函數集中的位置
        handler.i = set.length
        //將函數存入函數集中
        set.push(handler)
        element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
      })
  }
  //刪除綁定在元素上的指定類型的事件監聽函數,可同時刪除多種事件類型指定的函數,用數組或者還空格的字符串便可,同add

  function remove(element, events, fn, selector, capture) {
    var id = zid(element)
    eachEvent(events || '', fn, function(event, fn) {
      findHandlers(element, event, fn, selector).forEach(function(handler) {
        delete handlers[id][handler.i]
        element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
      })
    })
  }

  $.event = {
    add: add,
    remove: remove
  }

  //設置代理
  $.proxy = function(fn, context) {
    if ($.isFunction(fn)) {
      //若是fn是函數,則申明一個新的函數並用context做爲上下文調用fn
      var proxyFn = function() {
        return fn.apply(context, arguments)
      }
      //引用fn標示符
      proxyFn._zid = zid(fn)
      return proxyFn
    } else if (typeof context == 'string') {
      return $.proxy(fn[context], fn)
    } else {
      throw new TypeError("expected function")
    }
  }

  $.fn.bind = function(event, callback) {
    return this.each(function() {
      add(this, event, callback)
    })
  }
  $.fn.unbind = function(event, callback) {
    return this.each(function() {
      remove(this, event, callback)
    })
  }
  //綁定一次性事件監聽函數
  $.fn.one = function(event, callback) {
    return this.each(function(i, element) {
      //添加函數,而後在回調函數裏再刪除綁定。達到一次性事件的目的
      add(this, event, callback, null, function(fn, type) {
        return function() {
          var result = fn.apply(element, arguments) //這裏執行綁定的回調
          remove(element, type, fn) //刪除上面的綁定
          return result
        }
      })
    })
  }

  var returnTrue = function() {
    return true
  },
  returnFalse = function() {
    return false
  },
  ignoreProperties = /^([A-Z]|layer[XY]$)/,
    eventMethods = {
      preventDefault: 'isDefaultPrevented', //是否調用過preventDefault方法
      //取消執行其餘的事件處理函數並取消事件冒泡.若是同一個事件綁定了多個事件處理函數, 在其中一個事件處理函數中調用此方法後將不會繼續調用其餘的事件處理函數.
      stopImmediatePropagation: 'isImmediatePropagationStopped', //是否調用過stopImmediatePropagation方法,
      stopPropagation: 'isPropagationStopped' //是否調用過stopPropagation方法
    }
    //建立事件代理

    function createProxy(event) {
      var key, proxy = {
        originalEvent: event
      } //保存原始event
      for (key in event)
      if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key] //複製event屬性至proxy

      //將preventDefault,stopImmediatePropagatio,stopPropagation方法定義到proxy上
      $.each(eventMethods, function(name, predicate) {
        proxy[name] = function() {
          this[predicate] = returnTrue
          return event[name].apply(event, arguments)
        }
        proxy[predicate] = returnFalse
      })
      return proxy
    }

    // emulates the 'defaultPrevented' property for browsers that have none
    //event.defaultPrevented返回一個布爾值,代表當前事件的默認動做是否被取消,也就是是否執行了 event.preventDefault()方法.

    function fix(event) {
      if (!('defaultPrevented' in event)) {
        event.defaultPrevented = false //初始值false
        var prevent = event.preventDefault // 引用默認preventDefault
        event.preventDefault = function() { //重寫preventDefault
          this.defaultPrevented = true
          prevent.call(this)
        }
      }
    }
    //事件委託
    $.fn.delegate = function(selector, event, callback) {
      return this.each(function(i, element) {
        add(element, event, callback, selector, function(fn) {
          return function(e) {
            //若是事件對象是element裏的元素,取與selector相匹配的
            var evt, match = $(e.target).closest(selector, element).get(0)
            if (match) {
              //evt成了一個擁有preventDefault,stopImmediatePropagatio,stopPropagation方法,currentTarge,liveFiredn屬性的對象,另也有e的默認屬性
              evt = $.extend(createProxy(e), {
                currentTarget: match,
                liveFired: element
              })
              return fn.apply(match, [evt].concat([].slice.call(arguments, 1)))
            }
          }
        })
      })
    }
    //取消事件委託
    $.fn.undelegate = function(selector, event, callback) {
      return this.each(function() {
        remove(this, event, callback, selector)
      })
    }

  $.fn.live = function(event, callback) {
    $(document.body).delegate(this.selector, event, callback)
    return this
  }
  $.fn.die = function(event, callback) {
    $(document.body).undelegate(this.selector, event, callback)
    return this
  }

  //on也有live和事件委託的效果,因此能夠只用on來綁定事件
  $.fn.on = function(event, selector, callback) {
    return !selector || $.isFunction(selector) ? this.bind(event, selector || callback) : this.delegate(selector, event, callback)
  }
  $.fn.off = function(event, selector, callback) {
    return !selector || $.isFunction(selector) ? this.unbind(event, selector || callback) : this.undelegate(selector, event, callback)
  }
  //主動觸發事件
  $.fn.trigger = function(event, data) {
    if (typeof event == 'string' || $.isPlainObject(event)) event = $.Event(event)
    fix(event)
    event.data = data
    return this.each(function() {
      // items in the collection might not be DOM elements
      // (todo: possibly support events on plain old objects)
      if ('dispatchEvent' in this) this.dispatchEvent(event)
    })
  }

  // triggers event handlers on current element just as if an event occurred,
  // doesn't trigger an actual event, doesn't bubble
  //觸發元素上綁定的指定類型的事件,可是不冒泡
  $.fn.triggerHandler = function(event, data) {
    var e, result
    this.each(function(i, element) {
      e = createProxy(typeof event == 'string' ? $.Event(event) : event)
      e.data = data
      e.target = element
      //遍歷元素上綁定的指定類型的事件處理函數集,按順序執行,若是執行過stopImmediatePropagation,
      //那麼e.isImmediatePropagationStopped()就會返回true,再外層函數返回false
      //注意each裏的回調函數指定返回false時,會跳出循環,這樣就達到的中止執行回面函數的目的
      $.each(findHandlers(element, event.type || event), function(i, handler) {
        result = handler.proxy(e)
        if (e.isImmediatePropagationStopped()) return false
      })
    })
    return result
  }

  // shortcut methods for `.bind(event, fn)` for each event type
  ;
  ('focusin focusout load resize scroll unload click dblclick ' +
    'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave ' +
    'change select keydown keypress keyup error').split(' ').forEach(function(event) {
    $.fn[event] = function(callback) {
      return callback ?
      //若是有callback回調,則認爲它是綁定
      this.bind(event, callback) :
      //若是沒有callback回調,則讓它主動觸發
      this.trigger(event)
    }
  })

  ;
  ['focus', 'blur'].forEach(function(name) {
    $.fn[name] = function(callback) {
      if (callback) this.bind(name, callback)
      else this.each(function() {
        try {
          this[name]()
        } catch (e) {}
      })
      return this
    }
  })

  //根據參數建立一個event對象
  $.Event = function(type, props) {
    //當type是個對象時
    if (typeof type != 'string') props = type, type = props.type
    //建立一個event對象,若是是click,mouseover,mouseout時,建立的是MouseEvent,bubbles爲是否冒泡
    var event = document.createEvent(specialEvents[type] || 'Events'),
      bubbles = true
      //確保bubbles的值爲true或false,並將props參數的屬性擴展到新建立的event對象上
    if (props) for (var name in props)(name == 'bubbles') ? (bubbles = !! props[name]) : (event[name] = props[name])
    //初始化event對象,type爲事件類型,如click,bubbles爲是否冒泡,第三個參數表示是否能夠用preventDefault方法來取消默認操做
    event.initEvent(type, bubbles, true, null, null, null, null, null, null, null, null, null, null, null, null)
    //添加isDefaultPrevented方法,event.defaultPrevented返回一個布爾值,代表當前事件的默認動做是否被取消,也就是是否執行了 event.preventDefault()方法.
    event.isDefaultPrevented = function() {
      return this.defaultPrevented
    }
    return event
  }

})(Zepto)

/**
  Ajax處理部份
**/
;
(function($) {
  var jsonpID = 0,
    document = window.document,
    key,
    name,
    rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
    scriptTypeRE = /^(?:text|application)\/javascript/i,
    xmlTypeRE = /^(?:text|application)\/xml/i,
    jsonType = 'application/json',
    htmlType = 'text/html',
    blankRE = /^\s*$/

    // trigger a custom event and return false if it was cancelled

    function triggerAndReturn(context, eventName, data) {
      var event = $.Event(eventName)
      $(context).trigger(event, data)
      return !event.defaultPrevented
    }

    // trigger an Ajax "global" event
    //觸發 ajax的全局事件

    function triggerGlobal(settings, context, eventName, data) {
      if (settings.global) return triggerAndReturn(context || document, eventName, data)
    }

    // Number of active Ajax requests
    $.active = 0

    //settings.global爲true時表示須要觸發全局ajax事件
    //注意這裏的$.active++ === 0很巧妙,用它來判斷開始,由於只有$.active等於0時$.active++ === 0才成立

    function ajaxStart(settings) {
      if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart')
    }
    //注意這裏的 !(--$.active)同上面的殊途同歸,--$.active爲0,則表示$.active的值爲1,這樣用來判斷結束,也頗有意思

    function ajaxStop(settings) {
      if (settings.global && !(--$.active)) triggerGlobal(settings, null, 'ajaxStop')
    }

    // triggers an extra global event "ajaxBeforeSend" that's like "ajaxSend" but cancelable
    //觸發全局ajaxBeforeSend事件,若是返回false,則取消這次請求

    function ajaxBeforeSend(xhr, settings) {
      var context = settings.context
      if (settings.beforeSend.call(context, xhr, settings) === false || triggerGlobal(settings, context, 'ajaxBeforeSend', [xhr, settings]) === false) return false

      triggerGlobal(settings, context, 'ajaxSend', [xhr, settings])
    }

    function ajaxSuccess(data, xhr, settings) {
      var context = settings.context,
        status = 'success'
      settings.success.call(context, data, status, xhr)
      triggerGlobal(settings, context, 'ajaxSuccess', [xhr, settings, data])
      ajaxComplete(status, xhr, settings)
    }
    // type: "timeout", "error", "abort", "parsererror"

    function ajaxError(error, type, xhr, settings) {
      var context = settings.context
      settings.error.call(context, xhr, type, error)
      triggerGlobal(settings, context, 'ajaxError', [xhr, settings, error])
      ajaxComplete(type, xhr, settings)
    }
    // status: "success", "notmodified", "error", "timeout", "abort", "parsererror"

    function ajaxComplete(status, xhr, settings) {
      var context = settings.context
      settings.complete.call(context, xhr, status)
      triggerGlobal(settings, context, 'ajaxComplete', [xhr, settings])
      ajaxStop(settings)
    }

    // Empty function, used as default callback

    function empty() {}
    //可參考http://zh.wikipedia.org/zh-cn/JSONP
    $.ajaxJSONP = function(options) {
      if (!('type' in options)) return $.ajax(options)

      var callbackName = 'jsonp' + (++jsonpID), //建立回調函數名
        script = document.createElement('script'),
        //js文件加載完畢
        cleanup = function() {
          clearTimeout(abortTimeout) //清除下面的timeout事件處理
          $(script).remove() //移除建立的script標籤,由於該文件的JS內容已經解析過了
          delete window[callbackName] //清除掉指定的回調函數
        },
        //取消加載
        abort = function(type) {
          cleanup()
          // In case of manual abort or timeout, keep an empty function as callback
          // so that the SCRIPT tag that eventually loads won't result in an error.
          //這裏經過將回調函數從新賦值爲空函數來達到看似阻止加載JS的目的,實際上給script標籤設置了src屬性後,請求就已經產生了,而且不能中斷
          if (!type || type == 'timeout') window[callbackName] = empty
          ajaxError(null, type || 'abort', xhr, options)
        },
        xhr = {
          abort: abort
        }, abortTimeout

      if (ajaxBeforeSend(xhr, options) === false) {
        abort('abort')
        return false
      }
      //成功加載後的回調函數
      window[callbackName] = function(data) {
        cleanup()
        ajaxSuccess(data, xhr, options)
      }

      script.onerror = function() {
        abort('error')
      }
      //將回調函數名追加到請求地址,並賦給script,至此請求產生
      script.src = options.url.replace(/=\?/, '=' + callbackName)
      $('head').append(script)

      //若是設置了超時處理
      if (options.timeout > 0) abortTimeout = setTimeout(function() {
        abort('timeout')
      }, options.timeout)

      return xhr
    }

    //ajax全局設置
    $.ajaxSettings = {
      // Default type of request
      type: 'GET',
      // Callback that is executed before request
      beforeSend: empty,
      // Callback that is executed if the request succeeds
      success: empty,
      // Callback that is executed the the server drops error
      error: empty,
      // Callback that is executed on request complete (both: error and success)
      complete: empty,
      // The context for the callbacks
      context: null,
      // Whether to trigger "global" Ajax events
      global: true,
      // Transport
      xhr: function() {
        return new window.XMLHttpRequest()
      },
      // MIME types mapping
      accepts: {
        script: 'text/javascript, application/javascript',
        json: jsonType,
        xml: 'application/xml, text/xml',
        html: htmlType,
        text: 'text/plain'
      },
      // Whether the request is to another domain
      crossDomain: false,
      // Default timeout
      timeout: 0,
      // Whether data should be serialized to string
      processData: true,
      // Whether the browser should be allowed to cache GET responses
      cache: true
    };

  //根據MIME返回相應的數據類型,用做ajax參數裏的dataType用,設置預期返回的數據類型
  //如html,json,scirpt,xml,text

  function mimeToDataType(mime) {
    if (mime) mime = mime.split(';', 2)[0]
    return mime && (mime == htmlType ? 'html' : mime == jsonType ? 'json' : scriptTypeRE.test(mime) ? 'script' : xmlTypeRE.test(mime) && 'xml') || 'text'
  }
  //將查詢字符串追加到URL後面

  function appendQuery(url, query) {
    //注意這裏的replace,將第一個匹配到的&或者&&,&?,? ?& ??替換成?,用來保證地址的正確性
    return (url + '&' + query).replace(/[&?]{1,2}/, '?')
  }

  // serialize payload and append it to the URL for GET requests
  //序列化發送到服務器上的數據,若是是GET請求,則將序列化後的數據追加到請求地址後面

  function serializeData(options) {
    //options.processData表示對於非Get請求,是否自動將 options.data轉換爲字符串,前提是options.data不是字符串
    if (options.processData && options.data && $.type(options.data) != "string")
    //options.traditional表示是否以$.param方法序列化
    options.data = $.param(options.data, options.traditional)
    if (options.data && (!options.type || options.type.toUpperCase() == 'GET'))
    //若是是GET請求,將序列化後的數據追加到請求地址後面
    options.url = appendQuery(options.url, options.data)
  }

  $.ajax = function(options) {
    //注意這裏不能直接將$.ajaxSettings替換掉$.extend的第一個參數,這樣會改變 $.ajaxSettings裏面的值
    //這裏的作法是建立一個新對象
    var settings = $.extend({}, options || {})
    //若是它沒有定義$.ajaxSettings裏面的屬性的時候,纔去將$.ajaxSettings[key] 複製過來
    for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key]
    //執行全局ajaxStart
    ajaxStart(settings)

    //經過判斷請求地址和當前頁面地址的host是否相同來設置是跨域
    if (!settings.crossDomain) settings.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(settings.url) && RegExp.$2 != window.location.host
    //若是沒有設置請求地址,則取當前頁面地址
    if (!settings.url) settings.url = window.location.toString();
    //將data進行轉換
    serializeData(settings);
    //若是不設置緩存
    if (settings.cache === false) settings.url = appendQuery(settings.url, '_=' + Date.now())

    //若是請求的是jsonp,則將地址欄裏的=?替換爲callback=?,至關於一個簡寫
    var dataType = settings.dataType,
      hasPlaceholder = /=\?/.test(settings.url)
      if (dataType == 'jsonp' || hasPlaceholder) {
        if (!hasPlaceholder) settings.url = appendQuery(settings.url, 'callback=?')
        return $.ajaxJSONP(settings)
      }

    var mime = settings.accepts[dataType],
      baseHeaders = {},
      //若是請求地址沒有定請求協議,則與當前頁面協議相同
      protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol,
      xhr = settings.xhr(),
      abortTimeout
      //若是沒有跨域
    if (!settings.crossDomain) baseHeaders['X-Requested-With'] = 'XMLHttpRequest'
    if (mime) {
      baseHeaders['Accept'] = mime
      if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0]
      xhr.overrideMimeType && xhr.overrideMimeType(mime)
    }
    //若是不是GET請求,設置發送信息至服務器時內容編碼類型
    if (settings.contentType || (settings.contentType !== false && settings.data && settings.type.toUpperCase() != 'GET')) baseHeaders['Content-Type'] = (settings.contentType || 'application/x-www-form-urlencoded')
    settings.headers = $.extend(baseHeaders, settings.headers || {})

    xhr.onreadystatechange = function() {
      if (xhr.readyState == 4) {
        xhr.onreadystatechange = empty;
        clearTimeout(abortTimeout)
        var result, error = false
        //根據狀態來判斷請求是否成功
        //狀態>=200 && < 300 表示成功
        //狀態 == 304 表示文件未改動過,也可認爲成功
        //若是是取要本地文件那也能夠認爲是成功的,xhr.status == 0是在直接打開頁面時發生請求時出現的狀態,也就是否是用localhost的形式訪問的頁面的狀況
        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) {
          //獲取返回的數據類型
          dataType = dataType || mimeToDataType(xhr.getResponseHeader('content-type'))
          result = xhr.responseText

          try {
            // http://perfectionkills.com/global-eval-what-are-the-options/
            if (dataType == 'script')(1, eval)(result) //若是返回的數據類型是JS
            else if (dataType == 'xml') result = xhr.responseXML
            else if (dataType == 'json') result = blankRE.test(result) ? null : $.parseJSON(result)
          } catch (e) {
            error = e
          }
          //若是解析出錯,則執行全局parsererror事件
          if (error) ajaxError(error, 'parsererror', xhr, settings)
          //不然執行ajaxSuccess
          else ajaxSuccess(result, xhr, settings)
        } else {
          //若是請求出錯,則根據xhr.status來執行相應的錯誤處理函數
          ajaxError(null, xhr.status ? 'error' : 'abort', xhr, settings)
        }
      }
    }

    var async = 'async' in settings ? settings.async : true
    xhr.open(settings.type, settings.url, async)
    //設置請求頭信息
    for (name in settings.headers) xhr.setRequestHeader(name, settings.headers[name])

    //若是ajaxBeforeSend函數返回的false,則取消這次請示
    if (ajaxBeforeSend(xhr, settings) === false) {
      xhr.abort()
      return false
    }

    //當設置了settings.timeout,則在超時後取消請求,並執行timeout事件處理函數
    if (settings.timeout > 0) abortTimeout = setTimeout(function() {
      xhr.onreadystatechange = empty
      xhr.abort()
      ajaxError(null, 'timeout', xhr, settings)
    }, settings.timeout)

    // avoid sending empty string (#319)
    xhr.send(settings.data ? settings.data : null)
    return xhr
  }

  // handle optional data/success arguments
  //將參數轉換成ajax函數指定的參數格式

  function parseArguments(url, data, success, dataType) {
    var hasData = !$.isFunction(data) //若是data是function,則認爲它是請求成功後的回調
    return {
      url: url,
      data: hasData ? data : undefined, //若是data不是function實例
      success: !hasData ? data : $.isFunction(success) ? success : undefined,
      dataType: hasData ? dataType || success : success
    }
  }

  //簡單的get請求
  $.get = function(url, data, success, dataType) {
    return $.ajax(parseArguments.apply(null, arguments))
  }

  $.post = function(url, data, success, dataType) {
    var options = parseArguments.apply(null, arguments)
    options.type = 'POST'
    return $.ajax(options)
  }

  $.getJSON = function(url, data, success) {
    var options = parseArguments.apply(null, arguments)
    options.dataType = 'json'
    return $.ajax(options)
  }

  //這裏的url能夠是http://www.xxxx.com selector這種形式,就是對加載進來的HTML對行一個篩選
  $.fn.load = function(url, data, success) {
    if (!this.length) return this
    //將請求地址用空格分開
    var self = this,
      parts = url.split(/\s/),
      selector,
      options = parseArguments(url, data, success),
      callback = options.success
    if (parts.length > 1) options.url = parts[0], selector = parts[1]
    //要對成功後的回調函數進行一個改寫,由於須要將加載進來的HTML添加進當前集合
    options.success = function(response) {
      //selector就是對請求到的數據就行一個篩選的條件,好比只獲取數據裏的類名爲.test的標籤
      self.html(selector ? $('<div>').html(response.replace(rscript, "")).find(selector) : response)
      //這裏纔是你寫的回調
      callback && callback.apply(self, arguments)
    }
    $.ajax(options)
    return this
  }

  var escape = encodeURIComponent

    function serialize(params, obj, traditional, scope) {
      var type, array = $.isArray(obj)
      $.each(obj, function(key, value) {
        type = $.type(value)
        //scope用做處理value也是object或者array的狀況
        //traditional表示是否以傳統的方式拼接數據,
        //傳統的意思就是好比現有一個數據{a:[1,2,3]},轉成查詢字符串後結果爲'a=1&a=2&a=3'
        //非傳統的的結果則是a[]=1&a[]=2&a[]=3
        if (scope) key = traditional ? scope : scope + '[' + (array ? '' : key) + ']'
        // handle data in serializeArray() format
        //當處理的數據爲[{},{},{}]這種狀況的時候,通常指的是序列化表單後的結果
        if (!scope && array) params.add(value.name, value.value)
        // recurse into nested objects
        //當value值是數組或者是對象且不是按傳統的方式序列化的時候,須要再次遍歷value
        else if (type == "array" || (!traditional && type == "object")) serialize(params, value, traditional, key)
        else params.add(key, value)
      })
    }
    //將obj轉換爲查詢字符串的格式,traditional表示是否轉換成傳統的方式,至於傳統的方式的意思看上面的註釋
    $.param = function(obj, traditional) {
      var params = []
      //注意這裏將add方法定到params,因此下面serialize時纔不須要返回數據
      params.add = function(k, v) {
        this.push(escape(k) + '=' + escape(v))
      }
      serialize(params, obj, traditional)
      return params.join('&').replace(/%20/g, '+')
    }
})(Zepto)

;
(function($) {
  //序列化表單,返回一個相似[{name:value},{name2:value2}]的數組
  $.fn.serializeArray = function() {
    var result = [],
      el
      //將集合中的第一個表單裏的全部表單元素轉成數組後進行遍歷
      $(Array.prototype.slice.call(this.get(0).elements)).each(function() {
        el = $(this)
        var type = el.attr('type')
        //判斷其type屬性,排除fieldset,submi,reset,button以及沒有被選中的radio和checkbox
        if (this.nodeName.toLowerCase() != 'fieldset' && !this.disabled && type != 'submit' && type != 'reset' && type != 'button' &&
        //注意這裏的寫法,當元素既不是radio也不是checkbox時,直接返回true,
        //當元素是radio或者checkbox時,會執行後面的this.checked,當radio或者checkbox被選中時this.checked獲得true值
        //這樣就能夠篩選中被選中的radio和checkbox了
        ((type != 'radio' && type != 'checkbox') || this.checked)) result.push({
          name: el.attr('name'),
          value: el.val()
        })
      })
      return result
  }
  //將表單的值轉成name1=value1&name2=value2的形式
  $.fn.serialize = function() {
    var result = []
    this.serializeArray().forEach(function(elm) {
      result.push(encodeURIComponent(elm.name) + '=' + encodeURIComponent(elm.value))
    })
    return result.join('&')
  }
  //表單提交
  $.fn.submit = function(callback) {
    if (callback) this.bind('submit', callback)
    else if (this.length) {
      var event = $.Event('submit')
      this.eq(0).trigger(event)
      if (!event.defaultPrevented) this.get(0).submit()
    }
    return this
  }

})(Zepto)

//CSS3動畫
;
(function($, undefined) {
  var prefix = '',
    eventPrefix, endEventName, endAnimationName,
    vendors = {
      Webkit: 'webkit',
      Moz: '',
      O: 'o',
      ms: 'MS'
    },
    document = window.document,
    testEl = document.createElement('div'),
    supportedTransforms = /^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i,
    transform,
    transitionProperty, transitionDuration, transitionTiming,
    animationName, animationDuration, animationTiming,
    cssReset = {}
    //將駝峯式的字符串轉成用-分隔的小寫形式,如borderWidth ==> border-width

    function dasherize(str) {
      return downcase(str.replace(/([a-z])([A-Z])/, '$1-$2'))
    }

    function downcase(str) {
      return str.toLowerCase()
    }
    //用於修正事件名

    function normalizeEvent(name) {
      return eventPrefix ? eventPrefix + name : downcase(name)
    }

    //根據瀏覽器的特性設置CSS屬性前輕輟和事件前輟,好比瀏覽器內核是webkit
    //那麼用於設置CSS屬性的前輟prefix就等於'-webkit-',用來修正事件名的前輟eventPrefix就是Webkit
    $.each(vendors, function(vendor, event) {
      if (testEl.style[vendor + 'TransitionProperty'] !== undefined) {
        prefix = '-' + downcase(vendor) + '-'
        eventPrefix = event
        return false
      }
    })

    transform = prefix + 'transform'
  cssReset[transitionProperty = prefix + 'transition-property'] = cssReset[transitionDuration = prefix + 'transition-duration'] = cssReset[transitionTiming = prefix + 'transition-timing-function'] = cssReset[animationName = prefix + 'animation-name'] = cssReset[animationDuration = prefix + 'animation-duration'] = cssReset[animationTiming = prefix + 'animation-timing-function'] = ''

  $.fx = {
    off: (eventPrefix === undefined && testEl.style.transitionProperty === undefined),
    speeds: {
      _default: 400,
      fast: 200,
      slow: 600
    },
    cssPrefix: prefix,
    transitionEnd: normalizeEvent('TransitionEnd'),
    animationEnd: normalizeEvent('AnimationEnd')
  }

  $.fn.animate = function(properties, duration, ease, callback) {
    if ($.isPlainObject(duration)) ease = duration.easing, callback = duration.complete, duration = duration.duration
    //若是duration是數字時,表示動畫持續時間,若是是字符串,則從$.fx.speeds中取出相對應的值,若是沒有找到相應的值,對取默認值
    if (duration) duration = (typeof duration == 'number' ? duration : ($.fx.speeds[duration] || $.fx.speeds._default)) / 1000
    return this.anim(properties, duration, ease, callback)
  }

  $.fn.anim = function(properties, duration, ease, callback) {
    var key, cssValues = {}, cssProperties, transforms = '',
      that = this,
      wrappedCallback, endEvent = $.fx.transitionEnd
      //動畫持續時間默認值
    if (duration === undefined) duration = 0.4
    //若是瀏覽器不支持CSS3的動畫,則duration=0,意思就是直接跳轉最終值
    if ($.fx.off) duration = 0

    //若是properties是一個動畫名keyframe
    if (typeof properties == 'string') {
      // keyframe animation
      cssValues[animationName] = properties
      cssValues[animationDuration] = duration + 's'
      cssValues[animationTiming] = (ease || 'linear')
      endEvent = $.fx.animationEnd
    } else {
      cssProperties = []
      // CSS transitions
      for (key in properties)
      //若是設置 的CSS屬性是變形之類的
      if (supportedTransforms.test(key)) transforms += key + '(' + properties[key] + ') '
      else cssValues[key] = properties[key], cssProperties.push(dasherize(key))

      if (transforms) cssValues[transform] = transforms, cssProperties.push(transform)
      if (duration > 0 && typeof properties === 'object') {
        cssValues[transitionProperty] = cssProperties.join(', ')
        cssValues[transitionDuration] = duration + 's'
        cssValues[transitionTiming] = (ease || 'linear')
      }
    }

    wrappedCallback = function(event) {
      if (typeof event !== 'undefined') {
        if (event.target !== event.currentTarget) return // makes sure the event didn't bubble from "below"
        $(event.target).unbind(endEvent, wrappedCallback)
      }
      $(this).css(cssReset)
      callback && callback.call(this)
    }
    //當能夠執行動畫的時候,那麼動畫結束後會執行回調,
    //若是不支持持續動畫,在直接設置最終值後,不會執行動畫結束回調
    if (duration > 0) this.bind(endEvent, wrappedCallback)

    // trigger page reflow so new elements can animate
    this.size() && this.get(0).clientLeft

    //設置
    this.css(cssValues)

    //當持續時間小於等於0時,馬上還原
    if (duration <= 0) setTimeout(function() {
      that.each(function() {
        wrappedCallback.call(this)
      })
    }, 0)

    return this
  }

  testEl = null
})(Zepto)
相關文章
相關標籤/搜索