Zepto v1.0-1源碼註解

   1 /* Zepto v1.0-1-ga3cab6c - polyfill zepto detect event ajax form fx - zeptojs.com/license */
   2 ;(function(undefined) {
   3   if (String.prototype.trim === undefined) // fix for iOS 3.2
   4   String.prototype.trim = function() {
   5     return this.replace(/^\s+|\s+$/g, '')
   6   }
   7 
   8   // For iOS 3.x
   9   // from https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/reduce
  10   //這個方法的做用就是累似一個累計處理的做用,將前一條數據的處理結果用做下一次的處理
  11   //好比[1,2,3,4,].reduce(function(x,y){ return x+y}); ==> ((1+2)+3)+4,
  12 
  13   if (Array.prototype.reduce === undefined) Array.prototype.reduce = function(fun) {
  14     if (this === void 0 || this === null) throw new TypeError()
  15     var t = Object(this),
  16       len = t.length >>> 0,
  17       k = 0,
  18       accumulator
  19     if (typeof fun != 'function') throw new TypeError()
  20     if (len == 0 && arguments.length == 1) throw new TypeError()
  21     //取初始值  
  22     if (arguments.length >= 2) accumulator = arguments[1] //若是參數長度大於2個,則將第二個參數做爲初始值
  23     else do {
  24       if (k in t) {
  25         accumulator = t[k++] //不然將數組的第一條數據做爲初紹值
  26         break
  27       }
  28       if (++k >= len) throw new TypeError() //什麼狀況下會執行到這裏來???
  29     } while (true)
  30     //遍歷數組,將前一次的結果傳入處理函數進行累計處理
  31     while (k < len) {
  32       if (k in t) accumulator = fun.call(undefined, accumulator, t[k], k, t)
  33       k++
  34     }
  35     return accumulator
  36   }
  37 
  38 })()
  39 
  40 var Zepto = (function() {
  41   var undefined, key, $, classList, emptyArray = [],
  42     slice = emptyArray.slice,
  43     filter = emptyArray.filter,
  44     document = window.document,
  45     elementDisplay = {}, classCache = {},
  46     getComputedStyle = document.defaultView.getComputedStyle,
  47     //設置CSS時,不用加px單位的屬性
  48     cssNumber = {
  49       'column-count': 1,
  50       'columns': 1,
  51       'font-weight': 1,
  52       'line-height': 1,
  53       'opacity': 1,
  54       'z-index': 1,
  55       'zoom': 1
  56     },
  57     //HTML代碼片段的正則
  58     fragmentRE = /^\s*<(\w+|!)[^>]*>/,
  59     //匹配非單獨一個閉合標籤的標籤,相似將<div></div>寫成了<div/>
  60     tagExpanderRE = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
  61     //根節點
  62     rootNodeRE = /^(?:body|html)$/i,
  63 
  64     //須要提供get和set的方法名
  65     // special attributes that should be get/set via method calls
  66     methodAttributes = ['val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset'],
  67     //相鄰節點的一些操做
  68     adjacencyOperators = ['after', 'prepend', 'before', 'append'],
  69     table = document.createElement('table'),
  70     tableRow = document.createElement('tr'),
  71     //這裏的用途是當須要給tr,tbody,thead,tfoot,td,th設置innerHTMl的時候,須要用其父元素做爲容器來裝載HTML字符串
  72     containers = {
  73       'tr': document.createElement('tbody'),
  74       'tbody': table,
  75       'thead': table,
  76       'tfoot': table,
  77       'td': tableRow,
  78       'th': tableRow,
  79       '*': document.createElement('div')
  80     },
  81     //當DOM ready的時候,document會有如下三種狀態的一種
  82     readyRE = /complete|loaded|interactive/,
  83     //class選擇器的正則
  84     classSelectorRE = /^\.([\w-]+)$/,
  85     //id選擇器的正則
  86     idSelectorRE = /^#([\w-]*)$/,
  87     //DOM標籤正則
  88     tagSelectorRE = /^[\w-]+$/,
  89     class2type = {},
  90     toString = class2type.toString,
  91     zepto = {},
  92     camelize, uniq,
  93     tempParent = document.createElement('div');
  94 
  95   //判斷一個元素是否匹配給定的選擇器
  96   zepto.matches = function(element, selector) {
  97     if (!element || element.nodeType !== 1) return false
  98     //引用瀏覽器提供的MatchesSelector方法
  99     var matchesSelector = element.webkitMatchesSelector || element.mozMatchesSelector || element.oMatchesSelector || element.matchesSelector
 100     if (matchesSelector) return matchesSelector.call(element, selector);
 101     //若是瀏覽器不支持MatchesSelector方法,則將節點放入一個臨時div節點,
 102     //再經過selector來查找這個div下的節點集,再判斷給定的element是否在節點集中,若是在,則返回一個非零(即非false)的數字
 103     // fall back to performing a selector:
 104     var match, parent = element.parentNode,temp = !parent
 105     //當element沒有父節點,那麼將其插入到一個臨時的div裏面
 106     if (temp)(parent = tempParent).appendChild(element)
 107     //將parent做爲上下文,來查找selector的匹配結果,並獲取element在結果集的索引,不存在時爲-1,再經過~-1轉成0,存在時返回一個非零的值
 108     match = ~zepto.qsa(parent, selector).indexOf(element)
 109     //將插入的節點刪掉
 110     temp && tempParent.removeChild(element)
 111     return match
 112   }
 113 
 114   //獲取對象類型 
 115 
 116   function type(obj) {
 117     //obj爲null或者undefined時,直接返回'null'或'undefined'
 118     return obj == null ? String(obj) : class2type[toString.call(obj)] || "object"
 119   }
 120 
 121   function isFunction(value) {
 122     return type(value) == "function"
 123   }
 124 
 125   function isWindow(obj) {
 126     return obj != null && obj == obj.window
 127   }
 128 
 129   function isDocument(obj) {
 130     return obj != null && obj.nodeType == obj.DOCUMENT_NODE
 131   }
 132 
 133   function isObject(obj) {
 134     return type(obj) == "object"
 135   }
 136   //對於經過字面量定義的對象和new Object的對象返回true,new Object時傳參數的返回false
 137   //可參考http://snandy.iteye.com/blog/663245
 138 
 139   function isPlainObject(obj) {
 140     return isObject(obj) && !isWindow(obj) && obj.__proto__ == Object.prototype
 141   }
 142 
 143   function isArray(value) {
 144     return value instanceof Array
 145   }
 146   //類數組,好比nodeList,這個只是作最簡單的判斷,若是給一個對象定義一個值爲數據的length屬性,它一樣會返回true
 147 
 148   function likeArray(obj) {
 149     return typeof obj.length == 'number'
 150   }
 151 
 152   //清除給定的參數中的null或undefined,注意0==null,'' == null爲false
 153 
 154   function compact(array) {
 155     return filter.call(array, function(item) {
 156       return item != null
 157     })
 158   }
 159   //相似獲得一個數組的副本
 160 
 161   function flatten(array) {
 162     return array.length > 0 ? $.fn.concat.apply([], array) : array
 163   }
 164   //將字符串轉成駝峯式的格式
 165   camelize = function(str) {
 166     return str.replace(/-+(.)?/g, function(match, chr) {
 167       return chr ? chr.toUpperCase() : ''
 168     })
 169   }
 170   //將字符串格式化成-拼接的形式,通常用在樣式屬性上,好比border-width
 171 
 172   function dasherize(str) {
 173     return str.replace(/::/g, '/') //將::替換成/
 174     .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') //在大小寫字符之間插入_,大寫在前,好比AAAbb,獲得AA_Abb
 175     .replace(/([a-z\d])([A-Z])/g, '$1_$2') //在大小寫字符之間插入_,小寫或數字在前,好比bbbAaa,獲得bbb_Aaa
 176     .replace(/_/g, '-') //將_替換成-
 177     .toLowerCase() //轉成小寫
 178   }
 179   //數組去重,若是該條數據在數組中的位置與循環的索引值不相同,則說明數組中有與其相同的值
 180   uniq = function(array) {
 181     return filter.call(array, function(item, idx) {
 182       return array.indexOf(item) == idx
 183     })
 184   }
 185 
 186   //將給定的參數生成正則
 187 
 188   function classRE(name) {
 189     //classCache,緩存正則
 190     return name in classCache ? classCache[name] : (classCache[name] = new RegExp('(^|\\s)' + name + '(\\s|$)'))
 191   }
 192   //給須要的樣式值後面加上'px'單位,除了cssNumber裏面的指定的那些
 193 
 194   function maybeAddPx(name, value) {
 195     return (typeof value == "number" && !cssNumber[dasherize(name)]) ? value + "px" : value
 196   }
 197   //獲取節點的默認display屬性
 198 
 199   function defaultDisplay(nodeName) {
 200     var element, display
 201     if (!elementDisplay[nodeName]) { //緩存裏不存在
 202       element = document.createElement(nodeName)
 203       document.body.appendChild(element)
 204       display = getComputedStyle(element, '').getPropertyValue("display")
 205       element.parentNode.removeChild(element)
 206       display == "none" && (display = "block") //當display等於none時,設置其值爲block,搞不懂爲毛要這樣
 207       elementDisplay[nodeName] = display //緩存元素的默認display屬性
 208     }
 209     return elementDisplay[nodeName]
 210   }
 211   //獲取指定元素的子節點(不包含文本節點),Firefox不支持children,因此只能經過篩選childNodes
 212 
 213   function children(element) {
 214     return 'children' in element ? slice.call(element.children) : $.map(element.childNodes, function(node) {
 215       if (node.nodeType == 1) return node
 216     })
 217   }
 218 
 219   // `$.zepto.fragment` takes a html string and an optional tag name
 220   // to generate DOM nodes nodes from the given html string.
 221   // The generated DOM nodes are returned as an array.
 222   // This function can be overriden in plugins for example to make
 223   // it compatible with browsers that don't support the DOM fully.
 224   zepto.fragment = function(html, name, properties) {
 225     //將相似<div class="test"/>替換成<div class="test"></div>,算是一種修復吧
 226     if (html.replace) html = html.replace(tagExpanderRE, "<$1></$2>")
 227     //給name取標籤名
 228     if (name === undefined) name = fragmentRE.test(html) && RegExp.$1
 229     //設置容器標籤名,若是不是tr,tbody,thead,tfoot,td,th,則容器標籤名爲div
 230     if (!(name in containers)) name = '*'
 231 
 232     var nodes, dom, container = containers[name] //建立容器
 233     container.innerHTML = '' + html //將html代碼片段放入容器
 234     //取容器的子節點,這樣就直接把字符串轉成DOM節點了
 235     dom = $.each(slice.call(container.childNodes), function() {
 236       container.removeChild(this) //逐個刪除
 237     })
 238     //若是properties是對象, 則將其看成屬性來給添加進來的節點進行設置
 239     if (isPlainObject(properties)) {
 240       nodes = $(dom) //將dom轉成zepto對象,爲了方便下面調用zepto上的方法
 241       //遍歷對象,設置屬性
 242       $.each(properties, function(key, value) {
 243         //若是設置的是'val', 'css', 'html', 'text', 'data', 'width', 'height', 'offset',則調用zepto上相對應的方法
 244         if (methodAttributes.indexOf(key) > -1) nodes[key](value)
 245         else nodes.attr(key, value)
 246       })
 247     }
 248     //返回將字符串轉成的DOM節點後的數組,好比'<li></li><li></li><li></li>'轉成[li,li,li]
 249     return dom
 250   }
 251 
 252   // `$.zepto.Z` swaps out the prototype of the given `dom` array
 253   // of nodes with `$.fn` and thus supplying all the Zepto functions
 254   // to the array. Note that `__proto__` is not supported on Internet
 255   // Explorer. This method can be overriden in plugins.
 256   zepto.Z = function(dom, selector) {
 257     dom = dom || []
 258     dom.__proto__ = $.fn //經過給dom設置__proto__屬性指向$.fn來達到繼承$.fn上全部方法的目的
 259     dom.selector = selector || ''
 260     return dom
 261   }
 262 
 263   // `$.zepto.isZ` should return `true` if the given object is a Zepto
 264   // collection. This method can be overriden in plugins.
 265   //判斷給定的參數是不是Zepto集
 266   zepto.isZ = function(object) {
 267     return object instanceof zepto.Z
 268   }
 269 
 270   // `$.zepto.init` is Zepto's counterpart to jQuery's `$.fn.init` and
 271   // takes a CSS selector and an optional context (and handles various
 272   // special cases).
 273   // This method can be overriden in plugins.
 274   zepto.init = function(selector, context) {
 275     // If nothing given, return an empty Zepto collection
 276     if (!selector) return zepto.Z() //沒有參數,返回空數組
 277     //若是selector是個函數,則在DOM ready的時候執行它
 278     else if (isFunction(selector)) return $(document).ready(selector)
 279     //若是selector是一個zepto.Z實例,則直接返回它本身
 280     else if (zepto.isZ(selector)) return selector
 281     else {
 282       var dom
 283       //若是selector是一個數組,則將其裏面的null,undefined去掉
 284       if (isArray(selector)) dom = compact(selector)
 285       //若是selector是個對象,注意DOM節點的typeof值也是object,因此在裏面還要再進行一次判斷
 286       else if (isObject(selector))
 287       //若是是申明的對象,如{}, 則將selector屬性copy到一個新對象,並將結果放入數組
 288       //若是是該對象是DOM,則直接放到數組中
 289       dom = [isPlainObject(selector) ? $.extend({}, selector) : selector], selector = null
 290       //若是selector是一段HTML代碼片段,則將其轉換成DOM節點
 291       else if (fragmentRE.test(selector)) dom = zepto.fragment(selector.trim(), RegExp.$1, context), selector = null
 292       //若是存在上下文context,則在上下文中查找selector,此時的selector爲普通的CSS選擇器
 293       else if (context !== undefined) return $(context).find(selector)
 294       //若是沒有給定上下文,則在document中查找selector,此時的selector爲普通的CSS選擇器
 295       else dom = zepto.qsa(document, selector)
 296       //最後將查詢結果轉換成zepto集合
 297       return zepto.Z(dom, selector)
 298     }
 299   }
 300 
 301   // `$` will be the base `Zepto` object. When calling this
 302   // function just call `$.zepto.init, which makes the implementation
 303   // details of selecting nodes and creating Zepto collections
 304   // patchable in plugins.
 305   $ = function(selector, context) {
 306     return zepto.init(selector, context)
 307   }
 308 
 309   //擴展,deep表示是否深度擴展
 310 
 311   function extend(target, source, deep) {
 312     for (key in source)
 313     //若是深度擴展
 314     if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
 315       //若是要擴展的數據是對象且target相對應的key不是對象
 316       if (isPlainObject(source[key]) && !isPlainObject(target[key])) target[key] = {}
 317       //若是要擴展的數據是數組且target相對應的key不是數組
 318       if (isArray(source[key]) && !isArray(target[key])) target[key] = []
 319       extend(target[key], source[key], deep)
 320     } else if (source[key] !== undefined) target[key] = source[key]
 321   }
 322 
 323   // Copy all but undefined properties from one or more
 324   // objects to the `target` object.
 325   $.extend = function(target) {
 326     var deep, args = slice.call(arguments, 1)
 327     if (typeof target == 'boolean') { //當第一個參數爲boolean類型的值時,表示是否深度擴展
 328       deep = target
 329       target = args.shift() //target取第二個參數
 330     }
 331     //遍歷後面的參數,所有擴展到target上
 332     args.forEach(function(arg) {
 333       extend(target, arg, deep)
 334     })
 335     return target
 336   }
 337 
 338   // `$.zepto.qsa` is Zepto's CSS selector implementation which
 339   // uses `document.querySelectorAll` and optimizes for some special cases, like `#id`.
 340   // This method can be overriden in plugins.
 341   zepto.qsa = function(element, selector) {
 342     var found
 343     //當element爲document,且selector爲ID選擇器時
 344     return (isDocument(element) && idSelectorRE.test(selector)) ?
 345     //直接返回document.getElementById,RegExp.$1爲ID的值,當沒有找節點時返回[]
 346     ((found = element.getElementById(RegExp.$1)) ? [found] : []) :
 347     //當element不爲元素節點或者document時,返回[]
 348     (element.nodeType !== 1 && element.nodeType !== 9) ? [] :
 349     //不然將獲取到的結果轉成數組並返回
 350     slice.call(
 351     //若是selector是標籤名,直接調用getElementsByClassName
 352     classSelectorRE.test(selector) ? element.getElementsByClassName(RegExp.$1) :
 353     //若是selector是標籤名,直接調用getElementsByTagName
 354     tagSelectorRE.test(selector) ? element.getElementsByTagName(selector) :
 355     //不然調用querySelectorAll
 356     element.querySelectorAll(selector))
 357   }
 358 
 359   //在結果中進行過濾
 360 
 361   function filtered(nodes, selector) {
 362     return selector === undefined ? $(nodes) : $(nodes).filter(selector)
 363   }
 364   //判斷parent是否包含node
 365   $.contains = function(parent, node) {
 366     return parent !== node && parent.contains(node)
 367   }
 368 
 369   //這個函數在整個庫中取着很得要的做用,處理arg爲函數或者值的狀況
 370   //下面不少設置元素屬性時的函數都有用到
 371 
 372   function funcArg(context, arg, idx, payload) {
 373     return isFunction(arg) ? arg.call(context, idx, payload) : arg
 374   }
 375 
 376   function setAttribute(node, name, value) {
 377     //若是設置的值爲null或undefined,則至關於刪除該屬性,不然設置name屬性爲value
 378     value == null ? node.removeAttribute(name) : node.setAttribute(name, value)
 379   }
 380 
 381   // access className property while respecting SVGAnimatedString
 382 
 383   function className(node, value) {
 384     var klass = node.className,
 385       svg = klass && klass.baseVal !== undefined
 386 
 387     if (value === undefined) return svg ? klass.baseVal : klass
 388     svg ? (klass.baseVal = value) : (node.className = value)
 389   }
 390 
 391   // "true"  => true
 392   // "false" => false
 393   // "null"  => null
 394   // "42"    => 42
 395   // "42.5"  => 42.5
 396   // JSON    => parse if valid
 397   // String  => self
 398 
 399   function deserializeValue(value) {
 400     var num
 401     try {
 402       return value ? value == "true" || (value == "false" ? false : value == "null" ? null : !isNaN(num = Number(value)) ? num : /^[\[\{]/.test(value) ? $.parseJSON(value) : value) : value
 403     } catch (e) {
 404       return value
 405     }
 406   }
 407 
 408   $.type = type
 409   $.isFunction = isFunction
 410   $.isWindow = isWindow
 411   $.isArray = isArray
 412   $.isPlainObject = isPlainObject
 413 
 414   //空對象
 415   $.isEmptyObject = function(obj) {
 416     var name
 417     for (name in obj) return false
 418     return true
 419   }
 420 
 421   //獲取指定的值在數組中的位置
 422   $.inArray = function(elem, array, i) {
 423     return emptyArray.indexOf.call(array, elem, i)
 424   }
 425   //將字符串轉成駝峯式的格式
 426   $.camelCase = camelize
 427   //去字符串頭尾空格
 428   $.trim = function(str) {
 429     return str.trim()
 430   }
 431 
 432   // plugin compatibility
 433   $.uuid = 0
 434   $.support = {}
 435   $.expr = {}
 436 
 437   //遍歷elements,將每條記錄放入callback裏進憲處理,保存處理函數返回值不爲null或undefined的結果
 438   //注意這裏沒有統一的用for in,是爲了不遍歷數據默認屬性的狀況,如數組的toString,valueOf
 439   $.map = function(elements, callback) {
 440     var value, values = [],
 441       i, key
 442       //若是被遍歷的數據是數組或者nodeList
 443     if (likeArray(elements)) for (i = 0; i < elements.length; i++) {
 444       value = callback(elements[i], i)
 445       if (value != null) values.push(value)
 446     } else
 447     //若是是對象
 448     for (key in elements) {
 449       value = callback(elements[key], key)
 450       if (value != null) values.push(value)
 451     }
 452     return flatten(values)
 453   }
 454 
 455   //遍歷數組,將每條數據做爲callback的上下文,並傳入數據以及數據的索引進行處理,若是其中一條數據的處理結果明確返回false,
 456   //則中止遍歷,並返回elements
 457   $.each = function(elements, callback) {
 458     var i, key
 459     if (likeArray(elements)) {
 460       for (i = 0; i < elements.length; i++)
 461       if (callback.call(elements[i], i, elements[i]) === false) return elements
 462     } else {
 463       for (key in elements)
 464       if (callback.call(elements[key], key, elements[key]) === false) return elements
 465     }
 466 
 467     return elements
 468   }
 469   //過濾
 470   $.grep = function(elements, callback) {
 471     return filter.call(elements, callback)
 472   }
 473 
 474   if (window.JSON) $.parseJSON = JSON.parse
 475 
 476   // Populate the class2type map
 477   //填充class2type的值
 478   $.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
 479     class2type["[object " + name + "]"] = name.toLowerCase()
 480   })
 481 
 482   //針對DOM的一些操做
 483   // Define methods that will be available on all
 484   // Zepto collections
 485   $.fn = {
 486     // Because a collection acts like an array
 487     // copy over these useful array functions.
 488     forEach: emptyArray.forEach,
 489     reduce: emptyArray.reduce,
 490     push: emptyArray.push,
 491     sort: emptyArray.sort,
 492     indexOf: emptyArray.indexOf,
 493     concat: emptyArray.concat,
 494 
 495     // `map` and `slice` in the jQuery API work differently
 496     // from their array counterparts
 497     map: function(fn) {
 498       return $($.map(this, function(el, i) {
 499         return fn.call(el, i, el)
 500       }))
 501     },
 502     slice: function() {
 503       return $(slice.apply(this, arguments))
 504     },
 505     //DOM Ready
 506     ready: function(callback) {
 507       if (readyRE.test(document.readyState)) callback($)
 508       else document.addEventListener('DOMContentLoaded', function() {
 509         callback($)
 510       }, false)
 511       return this
 512     },
 513     //取集合中對應指定索引的值,若是idx小於0,則idx等於idx+length,length爲集合的長度
 514     get: function(idx) {
 515       return idx === undefined ? slice.call(this) : this[idx >= 0 ? idx : idx + this.length]
 516     },
 517     //將集合轉換爲數組
 518     toArray: function() {
 519       return this.get()
 520     },
 521     //獲取集合長度
 522     size: function() {
 523       return this.length
 524     },
 525     //將集合從dom中刪除
 526     remove: function() {
 527       return this.each(function() {
 528         if (this.parentNode != null) this.parentNode.removeChild(this)
 529       })
 530     },
 531     //遍歷集合,將集合中的每一項放入callback中進行處理,去掉結果爲false的項,注意這裏的callback若是明確返回false
 532     //那麼就會中止循環了
 533     each: function(callback) {
 534       emptyArray.every.call(this, function(el, idx) {
 535         return callback.call(el, idx, el) !== false
 536       })
 537       return this
 538     },
 539     //過濾集合,返回處理結果爲true的記錄
 540     filter: function(selector) {
 541       //this.not(selector)取到須要排除的集合,第二次再取反(這個時候this.not的參數就是一個集合了),獲得想要的集合
 542       if (isFunction(selector)) return this.not(this.not(selector))
 543       //filter收集返回結果爲true的記錄
 544       return $(filter.call(this, function(element) {
 545         return zepto.matches(element, selector) //當element與selector匹配,則收集
 546       }))
 547     },
 548     //將由selector獲取到的結果追加到當前集合中
 549     add: function(selector, context) {
 550       return $(uniq(this.concat($(selector, context)))) //追加並去重
 551     },
 552     //返回集合中的第1條記錄是否與selector匹配
 553     is: function(selector) {
 554       return this.length > 0 && zepto.matches(this[0], selector)
 555     },
 556     //排除集合裏知足條件的記錄,接收參數爲:css選擇器,function, dom ,nodeList
 557     not: function(selector) {
 558       var nodes = []
 559       //當selector爲函數時,safari下的typeof odeList也是function,因此這裏須要再加一個判斷selector.call !== undefined
 560       if (isFunction(selector) && selector.call !== undefined) {
 561         this.each(function(idx) {
 562           //注意這裏收集的是selector.call(this,idx)返回結果爲false的時候記錄
 563           if (!selector.call(this, idx)) nodes.push(this)
 564         })
 565       } else {
 566         //當selector爲字符串的時候,對集合進行篩選,也就是篩選出集合中知足selector的記錄
 567         var excludes = typeof selector == 'string' ? this.filter(selector) :
 568         //當selector爲nodeList時執行slice.call(selector),注意這裏的isFunction(selector.item)是爲了排除selector爲數組的狀況
 569         //當selector爲css選擇器,執行$(selector)
 570         (likeArray(selector) && isFunction(selector.item)) ? slice.call(selector) : $(selector)
 571         this.forEach(function(el) {
 572           //篩選出不在excludes集合裏的記錄,達到排除的目的
 573           if (excludes.indexOf(el) < 0) nodes.push(el)
 574         })
 575       }
 576       return $(nodes) //因爲上面獲得的結果是數組,這裏須要轉成zepto對象,以便繼承其它方法,實現鏈寫
 577     },
 578     /*
 579         接收node和string做爲參數,給當前集合篩選出包含selector的集合
 580         isObject(selector)是判斷參數是不是node,由於typeof node == 'object'
 581         當參數爲node時,只須要判讀當前記當裏是否包含node節點便可
 582         當參數爲string時,則在當前記錄裏查詢selector,若是長度爲0,則爲false,filter函數就會過濾掉這條記錄,不然保存該記錄
 583     */
 584     has: function(selector) {
 585       return this.filter(function() {
 586         return isObject(selector) ? $.contains(this, selector) : $(this).find(selector).size()
 587       })
 588     },
 589     /* 
 590         選擇集合中指定索引的記錄,當idx爲-1時,取最後一個記錄
 591     */
 592     eq: function(idx) {
 593       return idx === -1 ? this.slice(idx) : this.slice(idx, +idx + 1)
 594     },
 595     /* 
 596         取集合中的第一條記錄
 597     */
 598     first: function() {
 599       var el = this[0] //取集合中的第一條記錄
 600       //若是集合中的第一條數據自己就已是zepto對象則直接返回自己,不然轉成zepto對象
 601       //el && !isObject(el)在這裏取到一個判斷el是否爲節點的狀況,由於若是el是節點,那麼isObject(el)的結果就是true
 602       return el && !isObject(el) ? el : $(el)
 603     },
 604     /* 
 605         取集合中的最後一條記錄
 606     */
 607     last: function() {
 608       var el = this[this.length - 1] //取集合中的最後一條記錄
 609       //若是el爲node,則isObject(el)會爲true,須要轉成zepto對象
 610       return el && !isObject(el) ? el : $(el)
 611     },
 612     /* 
 613         在當前集合中查找selector,selector能夠是集合,選擇器,以及節點
 614     */
 615     find: function(selector) {
 616       var result, $this = this
 617       //若是selector爲node或者zepto集合時
 618       if (typeof selector == 'object')
 619       //遍歷selector,篩選出父級爲集合中記錄的selector
 620       result = $(selector).filter(function() {
 621         var node = this
 622         //若是$.contains(parent, node)返回true,則emptyArray.some也會返回true,外層的filter則會收錄該條記錄
 623         return emptyArray.some.call($this, function(parent) {
 624           return $.contains(parent, node)
 625         })
 626       })
 627       //若是selector是css選擇器
 628       //若是當前集合長度爲1時,調用zepto.qsa,將結果轉成zepto對象
 629       else if (this.length == 1) result = $(zepto.qsa(this[0], selector))
 630       //若是長度大於1,則調用map遍歷
 631       else result = this.map(function() {
 632         return zepto.qsa(this, selector)
 633       })
 634       return result
 635     },
 636     //取集合中第一記錄的最近的知足條件的父級元素
 637     closest: function(selector, context) {
 638       var node = this[0],
 639         collection = false
 640       if (typeof selector == 'object') collection = $(selector)
 641       //當selector是node或者zepto集合時,若是node不在collection集合中時須要取node.parentNode進行判斷
 642       //當selector是字符串選擇器時,若是node與selector不匹配,則須要取node.parentNode進行判斷
 643       while (node && !(collection ? collection.indexOf(node) >= 0 : zepto.matches(node, selector)))
 644       //當node 不是context,document的時候,取node.parentNode
 645       node = node !== context && !isDocument(node) && node.parentNode
 646       return $(node)
 647     },
 648     //取集合全部父級元素
 649     parents: function(selector) {
 650       var ancestors = [],
 651         nodes = this
 652         //經過遍歷nodes獲得全部父級,注意在while裏nodes被從新賦值了
 653         //本函數的巧妙之處在於,不停在獲取父級,再遍歷父級獲取父級的父級
 654         //而後再經過去重,獲得最終想要的結果,當到達最頂層的父級時,nodes.length就爲0了
 655       while (nodes.length > 0)
 656       //nodes被從新賦值爲收集到的父級集合
 657       nodes = $.map(nodes, function(node) {
 658         //遍歷nodes,收集集合的第一層父級
 659         //ancestors.indexOf(node) < 0用來去重複
 660         if ((node = node.parentNode) && !isDocument(node) && ancestors.indexOf(node) < 0) {
 661           ancestors.push(node) //收集已經獲取到的父級元素,用於去重複
 662           return node
 663         }
 664       })
 665       //上面還只是取到了全部的父級元素,這裏還須要對其進行篩選從而獲得最終想要的結果
 666       return filtered(ancestors, selector)
 667     },
 668     //獲取集合的父節點
 669     parent: function(selector) {
 670       return filtered(uniq(this.pluck('parentNode')), selector)
 671     },
 672     children: function(selector) {
 673       return filtered(this.map(function() {
 674         return children(this)
 675       }), selector)
 676     },
 677     contents: function() {
 678       return this.map(function() {
 679         return slice.call(this.childNodes)
 680       })
 681     },
 682     siblings: function(selector) {
 683       return filtered(this.map(function(i, el) {
 684         //先獲取該節點的父節點中的全部子節點,再排除自己
 685         return filter.call(children(el.parentNode), function(child) {
 686           return child !== el
 687         })
 688       }), selector)
 689     },
 690     empty: function() {
 691       return this.each(function() {
 692         this.innerHTML = ''
 693       })
 694     },
 695     //根據屬性來獲取當前集合的相關集合
 696     pluck: function(property) {
 697       return $.map(this, function(el) {
 698         return el[property]
 699       })
 700     },
 701     show: function() {
 702       return this.each(function() {
 703         //清除元素的內聯display="none"的樣式
 704         this.style.display == "none" && (this.style.display = null)
 705         //當樣式表裏的該元素的display樣式爲none時,設置它的display爲默認值
 706         if (getComputedStyle(this, '').getPropertyValue("display") == "none") this.style.display = defaultDisplay(this.nodeName) //defaultDisplay是獲取元素默認display的方法
 707       })
 708     },
 709     replaceWith: function(newContent) {
 710       //將要替換的內容插入到被替換的內容前面,而後刪除被替換的內容
 711       return this.before(newContent).remove()
 712     },
 713     wrap: function(structure) {
 714       var func = isFunction(structure)
 715       if (this[0] && !func)
 716       //若是structure是字符串,則將其轉成DOM
 717       var dom = $(structure).get(0),
 718         //若是structure是已經存在於頁面上的節點或者被wrap的記錄不僅一條,則須要clone dom
 719         clone = dom.parentNode || this.length > 1
 720 
 721       return this.each(function(index) {
 722         $(this).wrapAll(
 723         func ? structure.call(this, index) : clone ? dom.cloneNode(true) : dom)
 724       })
 725     },
 726     wrapAll: function(structure) {
 727       if (this[0]) {
 728         //將要包裹的內容插入到第一條記錄的前面,算是給structure定位圍置
 729         $(this[0]).before(structure = $(structure))
 730         var children
 731         // drill down to the inmost element
 732         //取structure裏的第一個子節點的最裏層
 733         while ((children = structure.children()).length) structure = children.first()
 734         //將當前集合插入到最裏層的節點裏,達到wrapAll的目的
 735         $(structure).append(this)
 736       }
 737       return this
 738     },
 739     //在匹配元素裏的內容外包一層結構
 740     wrapInner: function(structure) {
 741       var func = isFunction(structure)
 742       return this.each(function(index) {
 743         //原理就是獲取節點的內容,而後用structure將內容包起來,若是內容不存在,則直接將structure append到該節點
 744         var self = $(this),
 745           contents = self.contents(),
 746           dom = func ? structure.call(this, index) : structure
 747           contents.length ? contents.wrapAll(dom) : self.append(dom)
 748       })
 749     },
 750     unwrap: function() {
 751       //用子元素替換掉父級
 752       this.parent().each(function() {
 753         $(this).replaceWith($(this).children())
 754       })
 755       return this
 756     },
 757     //clone node
 758     clone: function() {
 759       return this.map(function() {
 760         return this.cloneNode(true)
 761       })
 762     },
 763     //隱藏集合
 764     hide: function() {
 765       return this.css("display", "none")
 766     },
 767     toggle: function(setting) {
 768       return this.each(function() {
 769         var el = $(this);
 770         /* 
 771             這個setting取得做用就是控制顯示與隱藏,並不切換,當它的值爲true時,一直顯示,false時,一直隱藏
 772             這個地方的判斷看上去有點繞,其實也簡單,意思是說,當不給toogle參數時,根據元素的display是否等於none來決定顯示或者隱藏元素
 773             當給toogle參數,就沒有切換效果了,只是簡單的根據參數值來決定顯示或隱藏。若是參數true,至關於show方法,false則至關於hide方法
 774         */
 775         (setting === undefined ? el.css("display") == "none" : setting) ? el.show() : el.hide()
 776       })
 777     },
 778     prev: function(selector) {
 779       return $(this.pluck('previousElementSibling')).filter(selector || '*')
 780     },
 781     next: function(selector) {
 782       return $(this.pluck('nextElementSibling')).filter(selector || '*')
 783     },
 784     //當有參數時,設置集合每條記錄的HTML,沒有參數時,則爲獲取集合第一條記錄的HTML,若是集合的長度爲0,則返回null
 785     html: function(html) {
 786       return html === undefined ?
 787       //參數html不存在時,獲取集合中第一條記錄的html
 788       (this.length > 0 ? this[0].innerHTML : null) :
 789       //不然遍歷集合,設置每條記錄的html
 790       this.each(function(idx) {
 791         //記錄原始的innerHTMl
 792         var originHtml = this.innerHTML
 793         //若是參數html是字符串直接插入到記錄中,
 794         //若是是函數,則將當前記錄做爲上下文,調用該函數,且傳入該記錄的索引和原始innerHTML做爲參數
 795         $(this).empty().append(funcArg(this, html, idx, originHtml))
 796       })
 797     },
 798     text: function(text) {
 799       //若是不給定text參數,則爲獲取功能,集合長度大於0時,取第一條數據的textContent,不然返回null,
 800       //若是給定text參數,則爲集合的每一條數據設置textContent爲text
 801       return text === undefined ? (this.length > 0 ? this[0].textContent : null) : this.each(function() {
 802         this.textContent = text
 803       })
 804     },
 805     attr: function(name, value) {
 806       var result
 807       //當只有name且爲字符串時,表示獲取第一條記錄的屬性
 808       return (typeof name == 'string' && value === undefined) ?
 809       //集合沒有記錄或者集合的元素不是node類型,返回undefined
 810       (this.length == 0 || this[0].nodeType !== 1 ? undefined :
 811       //若是取的是input的value
 812       (name == 'value' && this[0].nodeName == 'INPUT') ? this.val() :
 813       //注意直接定義在node上的屬性,在標準瀏覽器和ie9,10中用getAttribute取不到,獲得的結果是null
 814       //好比div.aa = 10,用div.getAttribute('aa')獲得的是null,須要用div.aa或者div['aa']這樣來取
 815       (!(result = this[0].getAttribute(name)) && name in this[0]) ? this[0][name] : result) :
 816       this.each(function(idx) {
 817         if (this.nodeType !== 1) return
 818         //若是name是一個對象,如{'id':'test','value':11},則給數據設置屬性
 819         if (isObject(name)) for (key in name) setAttribute(this, key, name[key])
 820         //若是name只是一個普通的屬性字符串,用funcArg來處理value是值或者function的狀況最終返回一個屬性值
 821         //若是funcArg函數返回的是undefined或者null,則至關於刪除元素的屬性
 822         else setAttribute(this, name, funcArg(this, value, idx, this.getAttribute(name)))
 823       })
 824     },
 825     removeAttr: function(name) {
 826       return this.each(function() {
 827         this.nodeType === 1 && setAttribute(this, name)//setAttribute的第三個參數爲null時,效果是刪除name屬性
 828       })
 829     },
 830     //獲取第一條數據的指定的name屬性或者給每條數據添加自定義屬性,注意和setAttribute的區別
 831     prop: function(name, value) {
 832       //沒有給定value時,爲獲取,給定value則給每一條數據添加,value能夠爲值也能夠是一個返回值的函數
 833       return (value === undefined) ? (this[0] && this[0][name]) : this.each(function(idx) {
 834         this[name] = funcArg(this, value, idx, this[name])
 835       })
 836     },
 837     data: function(name, value) {
 838       //經過調用attr方法來實現獲取與設置的效果,注意attr方法裏,當value存在的時候,返回的是集合自己,若是不存在,則是返回獲取的值
 839       var data = this.attr('data-' + dasherize(name), value)
 840       return data !== null ? deserializeValue(data) : undefined
 841     },
 842     val: function(value) {
 843       return (value === undefined) ?
 844       //若是是多選的select,則返回一個包含被選中的option的值的數組
 845       (this[0] && (this[0].multiple ? $(this[0]).find('option').filter(function(o) {
 846         return this.selected
 847       }).pluck('value') : this[0].value)) : this.each(function(idx) {
 848         this.value = funcArg(this, value, idx, this.value)
 849       })
 850     },
 851     offset: function(coordinates) {
 852       if (coordinates) return this.each(function(index) {
 853         var $this = $(this),
 854           //coordinates爲{}時直接返回,爲函數時返回處理結果給coords
 855           coords = funcArg(this, coordinates, index, $this.offset()),
 856           //取父級的offset
 857           parentOffset = $this.offsetParent().offset(),
 858           //計算出它們之間的差,得出其偏移量  
 859           props = {
 860             top: coords.top - parentOffset.top,
 861             left: coords.left - parentOffset.left
 862           }
 863           //注意元素的position爲static時,設置top,left是無效的
 864         if ($this.css('position') == 'static') props['position'] = 'relative'
 865         $this.css(props)
 866       })
 867       //取第一條記錄的offset,包括offsetTop,offsetLeft,offsetWidth,offsetHeight
 868       if (this.length == 0) return null
 869       var obj = this[0].getBoundingClientRect()
 870       //window.pageYOffset就是相似Math.max(document.documentElement.scrollTop||document.body.scrollTop)
 871       return {
 872         left: obj.left + window.pageXOffset,
 873         top: obj.top + window.pageYOffset,
 874         width: Math.round(obj.width),
 875         height: Math.round(obj.height)
 876       }
 877     },
 878     css: function(property, value) {
 879       //獲取指定的樣式
 880       if (arguments.length < 2 && typeof property == 'string') return this[0] && (this[0].style[camelize(property)] || getComputedStyle(this[0], '').getPropertyValue(property))
 881       //設置樣式
 882       var css = ''
 883       if (type(property) == 'string') {
 884         if (!value && value !== 0) //當value的值爲非零的能夠轉成false的值時如(null,undefined),刪掉property樣式
 885         this.each(function() {
 886           //style.removeProperty 移除指定的CSS樣式名(IE不支持DOM的style方法)
 887           this.style.removeProperty(dasherize(property))
 888         })
 889         else css = dasherize(property) + ":" + maybeAddPx(property, value)
 890       } else {
 891         //當property是對象時
 892         for (key in property)
 893         if (!property[key] && property[key] !== 0)
 894         //當property[key]的值爲非零的能夠轉成false的值時,刪掉key樣式
 895         this.each(function() {
 896           this.style.removeProperty(dasherize(key))
 897         })
 898         else css += dasherize(key) + ':' + maybeAddPx(key, property[key]) + ';'
 899       }
 900       //設置
 901       return this.each(function() {
 902         this.style.cssText += ';' + css
 903       })
 904     },
 905     index: function(element) {
 906       //這裏的$(element)[0]是爲了將字符串轉成node,由於this是個包含node的數組
 907       //當不指定element時,取集合中第一條記錄在其父節點的位置
 908       //this.parent().children().indexOf(this[0])這句很巧妙,和取第一記錄的parent().children().indexOf(this)相同
 909       return element ? this.indexOf($(element)[0]) : this.parent().children().indexOf(this[0])
 910     },
 911     hasClass: function(name) {
 912       return emptyArray.some.call(this, function(el) {
 913         //注意這裏的this是classRE(name)生成的正則
 914         return this.test(className(el))
 915       }, classRE(name))
 916     },
 917     addClass: function(name) {
 918       return this.each(function(idx) {
 919         classList = []
 920         var cls = className(this),
 921           newName = funcArg(this, name, idx, cls)
 922           //處理同時多個類的狀況,用空格分開
 923           newName.split(/\s+/g).forEach(function(klass) {
 924             if (!$(this).hasClass(klass)) classList.push(klass)
 925           }, this)
 926           classList.length && className(this, cls + (cls ? " " : "") + classList.join(" "))
 927       })
 928     },
 929     removeClass: function(name) {
 930       return this.each(function(idx) {
 931         if (name === undefined) return className(this, '')
 932         classList = className(this)
 933         funcArg(this, name, idx, classList).split(/\s+/g).forEach(function(klass) {
 934           classList = classList.replace(classRE(klass), " ")
 935         })
 936         className(this, classList.trim())
 937       })
 938     },
 939     toggleClass: function(name, when) {
 940       return this.each(function(idx) {
 941         var $this = $(this),
 942           names = funcArg(this, name, idx, className(this))
 943           names.split(/\s+/g).forEach(function(klass) {
 944             (when === undefined ? !$this.hasClass(klass) : when) ? $this.addClass(klass) : $this.removeClass(klass)
 945           })
 946       })
 947     },
 948     scrollTop: function() {
 949       if (!this.length) return
 950       return ('scrollTop' in this[0]) ? this[0].scrollTop : this[0].scrollY
 951     },
 952     position: function() {
 953       if (!this.length) return
 954 
 955       var elem = this[0],
 956         // Get *real* offsetParent
 957         offsetParent = this.offsetParent(),
 958         // Get correct offsets
 959         offset = this.offset(),
 960         parentOffset = rootNodeRE.test(offsetParent[0].nodeName) ? {
 961           top: 0,
 962           left: 0
 963         } : offsetParent.offset()
 964 
 965         // Subtract element margins
 966         // note: when an element has margin: auto the offsetLeft and marginLeft
 967         // are the same in Safari causing offset.left to incorrectly be 0
 968         offset.top -= parseFloat($(elem).css('margin-top')) || 0
 969         offset.left -= parseFloat($(elem).css('margin-left')) || 0
 970 
 971         // Add offsetParent borders
 972         parentOffset.top += parseFloat($(offsetParent[0]).css('border-top-width')) || 0
 973         parentOffset.left += parseFloat($(offsetParent[0]).css('border-left-width')) || 0
 974 
 975         // Subtract the two offsets
 976       return {
 977         top: offset.top - parentOffset.top,
 978         left: offset.left - parentOffset.left
 979       }
 980     },
 981     offsetParent: function() {
 982       return this.map(function() {
 983         var parent = this.offsetParent || document.body
 984         while (parent && !rootNodeRE.test(parent.nodeName) && $(parent).css("position") == "static")
 985         parent = parent.offsetParent
 986         return parent
 987       })
 988     }
 989   }
 990 
 991   // for now
 992   $.fn.detach = $.fn.remove
 993 
 994   // Generate the `width` and `height` functions
 995   ;
 996   ['width', 'height'].forEach(function(dimension) {
 997     $.fn[dimension] = function(value) {
 998       var offset, el = this[0],
 999         //將width,hegiht轉成Width,Height,用於取window或者document的width和height
1000         Dimension = dimension.replace(/./, function(m) {
1001           return m[0].toUpperCase()
1002         })
1003         //沒有參數爲獲取,獲取window的width和height用innerWidth,innerHeight
1004       if (value === undefined) return isWindow(el) ? el['inner' + Dimension] :
1005       //獲取document的width和height時,用offsetWidth,offsetHeight
1006       isDocument(el) ? el.documentElement['offset' + Dimension] : (offset = this.offset()) && offset[dimension]
1007       else return this.each(function(idx) {
1008         el = $(this)
1009         el.css(dimension, funcArg(this, value, idx, el[dimension]()))
1010       })
1011     }
1012   })
1013 
1014   function traverseNode(node, fun) {
1015     fun(node)
1016     for (var key in node.childNodes) traverseNode(node.childNodes[key], fun)
1017   }
1018 
1019   // Generate the `after`, `prepend`, `before`, `append`,
1020   // `insertAfter`, `insertBefore`, `appendTo`, and `prependTo` methods.
1021   adjacencyOperators.forEach(function(operator, operatorIndex) {
1022     var inside = operatorIndex % 2 //=> prepend, append
1023 
1024     $.fn[operator] = function() {
1025       // arguments can be nodes, arrays of nodes, Zepto objects and HTML strings
1026       var argType, nodes = $.map(arguments, function(arg) {
1027         argType = type(arg)
1028         return argType == "object" || argType == "array" || arg == null ? arg : zepto.fragment(arg)
1029       }),
1030         parent, copyByClone = this.length > 1 //若是集合的長度大於集,則須要clone被插入的節點
1031       if (nodes.length < 1) return this
1032 
1033       return this.each(function(_, target) {
1034         parent = inside ? target : target.parentNode
1035 
1036         //經過改變target將after,prepend,append操做轉成before操做,insertBefore的第二個參數爲null時等於appendChild操做
1037         target = operatorIndex == 0 ? target.nextSibling : operatorIndex == 1 ? target.firstChild : operatorIndex == 2 ? target : null
1038 
1039         nodes.forEach(function(node) {
1040           if (copyByClone) node = node.cloneNode(true)
1041           else if (!parent) return $(node).remove()
1042 
1043           //插入節點後,若是被插入的節點是SCRIPT,則執行裏面的內容並將window設爲上下文
1044           traverseNode(parent.insertBefore(node, target), function(el) {
1045             if (el.nodeName != null && el.nodeName.toUpperCase() === 'SCRIPT' && (!el.type || el.type === 'text/javascript') && !el.src) window['eval'].call(window, el.innerHTML)
1046           })
1047         })
1048       })
1049     }
1050 
1051     // after    => insertAfter
1052     // prepend  => prependTo
1053     // before   => insertBefore
1054     // append   => appendTo
1055     $.fn[inside ? operator + 'To' : 'insert' + (operatorIndex ? 'Before' : 'After')] = function(html) {
1056       $(html)[operator](this)
1057       return this
1058     }
1059   })
1060 
1061   zepto.Z.prototype = $.fn
1062 
1063   // Export internal API functions in the `$.zepto` namespace
1064   zepto.uniq = uniq
1065   zepto.deserializeValue = deserializeValue
1066   $.zepto = zepto
1067 
1068   return $
1069 })();
1070 
1071 window.Zepto = Zepto;
1072 '$' in window || (window.$ = Zepto);
1073 
1074 ;(function($) {
1075   function detect(ua) {
1076     var os = this.os = {}, browser = this.browser = {},
1077     webkit = ua.match(/WebKit\/([\d.]+)/),
1078       android = ua.match(/(Android)\s+([\d.]+)/),
1079       ipad = ua.match(/(iPad).*OS\s([\d_]+)/),
1080       iphone = !ipad && ua.match(/(iPhone\sOS)\s([\d_]+)/),
1081       webos = ua.match(/(webOS|hpwOS)[\s\/]([\d.]+)/),
1082       touchpad = webos && ua.match(/TouchPad/),
1083       kindle = ua.match(/Kindle\/([\d.]+)/),
1084       silk = ua.match(/Silk\/([\d._]+)/),
1085       blackberry = ua.match(/(BlackBerry).*Version\/([\d.]+)/),
1086       bb10 = ua.match(/(BB10).*Version\/([\d.]+)/),
1087       rimtabletos = ua.match(/(RIM\sTablet\sOS)\s([\d.]+)/),
1088       playbook = ua.match(/PlayBook/),
1089       chrome = ua.match(/Chrome\/([\d.]+)/) || ua.match(/CriOS\/([\d.]+)/),
1090       firefox = ua.match(/Firefox\/([\d.]+)/)
1091 
1092       // Todo: clean this up with a better OS/browser seperation:
1093       // - discern (more) between multiple browsers on android
1094       // - decide if kindle fire in silk mode is android or not
1095       // - Firefox on Android doesn't specify the Android version
1096       // - possibly devide in os, device and browser hashes
1097 
1098     if (browser.webkit = !! webkit) browser.version = webkit[1]
1099 
1100     if (android) os.android = true, os.version = android[2]
1101     if (iphone) os.ios = os.iphone = true, os.version = iphone[2].replace(/_/g, '.')
1102     if (ipad) os.ios = os.ipad = true, os.version = ipad[2].replace(/_/g, '.')
1103     if (webos) os.webos = true, os.version = webos[2]
1104     if (touchpad) os.touchpad = true
1105     if (blackberry) os.blackberry = true, os.version = blackberry[2]
1106     if (bb10) os.bb10 = true, os.version = bb10[2]
1107     if (rimtabletos) os.rimtabletos = true, os.version = rimtabletos[2]
1108     if (playbook) browser.playbook = true
1109     if (kindle) os.kindle = true, os.version = kindle[1]
1110     if (silk) browser.silk = true, browser.version = silk[1]
1111     if (!silk && os.android && ua.match(/Kindle Fire/)) browser.silk = true
1112     if (chrome) browser.chrome = true, browser.version = chrome[1]
1113     if (firefox) browser.firefox = true, browser.version = firefox[1]
1114 
1115     os.tablet = !! (ipad || playbook || (android && !ua.match(/Mobile/)) || (firefox && ua.match(/Tablet/)))
1116     os.phone = !! (!os.tablet && (android || iphone || webos || blackberry || bb10 || (chrome && ua.match(/Android/)) || (chrome && ua.match(/CriOS\/([\d.]+)/)) || (firefox && ua.match(/Mobile/))))
1117   }
1118 
1119   detect.call($, navigator.userAgent)
1120   // make available to unit tests
1121   $.__detect = detect
1122 
1123 })(Zepto)
1124 
1125 /* 
1126 事件處理部份
1127  */
1128 ;
1129 (function($) {
1130   var $$ = $.zepto.qsa,
1131     handlers = {}, _zid = 1,
1132     specialEvents = {},
1133     hover = {
1134       mouseenter: 'mouseover',
1135       mouseleave: 'mouseout'
1136     }
1137 
1138   specialEvents.click = specialEvents.mousedown = specialEvents.mouseup = specialEvents.mousemove = 'MouseEvents'
1139 
1140   //取element的惟一標示符,若是沒有,則設置一個並返回
1141 
1142   function zid(element) {
1143     return element._zid || (element._zid = _zid++)
1144   }
1145   //查找綁定在元素上的指定類型的事件處理函數集合
1146 
1147   function findHandlers(element, event, fn, selector) {
1148     event = parse(event)
1149     if (event.ns) var matcher = matcherFor(event.ns)
1150     return (handlers[zid(element)] || []).filter(function(handler) {
1151       return handler && (!event.e || handler.e == event.e) //判斷事件類型是否相同
1152       &&
1153       (!event.ns || matcher.test(handler.ns)) //判斷事件命名空間是否相同
1154       //注意函數是引用類型的數據zid(handler.fn)的做用是返回handler.fn的標示符,若是沒有,則給它添加一個,
1155       //這樣若是fn和handler.fn引用的是同一個函數,那麼fn上應該也可相同的標示符,
1156       //這裏就是經過這一點來判斷兩個變量是否引用的同一個函數
1157       &&
1158       (!fn || zid(handler.fn) === zid(fn)) && (!selector || handler.sel == selector)
1159     })
1160   }
1161   //解析事件類型,返回一個包含事件名稱和事件命名空間的對象
1162 
1163   function parse(event) {
1164     var parts = ('' + event).split('.')
1165     return {
1166       e: parts[0],
1167       ns: parts.slice(1).sort().join(' ')
1168     }
1169   }
1170   //生成命名空間的正則
1171 
1172   function matcherFor(ns) {
1173     return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)')
1174   }
1175   //遍歷events
1176 
1177   function eachEvent(events, fn, iterator) {
1178     if ($.type(events) != "string") $.each(events, iterator)
1179     else events.split(/\s/).forEach(function(type) {
1180       iterator(type, fn)
1181     })
1182   }
1183   //經過給focus和blur事件設置爲捕獲來達到事件冒泡的目的
1184 
1185   function eventCapture(handler, captureSetting) {
1186     return handler.del && (handler.e == 'focus' || handler.e == 'blur') || !! captureSetting
1187   }
1188 
1189   //修復不支持mouseenter和mouseleave的狀況
1190 
1191   function realEvent(type) {
1192     return hover[type] || type
1193   }
1194 
1195   //給元素綁定監聽事件,可同時綁定多個事件類型,如['click','mouseover','mouseout'],也能夠是'click mouseover mouseout'
1196 
1197   function add(element, events, fn, selector, getDelegate, capture) {
1198     var id = zid(element),
1199       set = (handlers[id] || (handlers[id] = [])) //元素上已經綁定的全部事件處理函數
1200       eachEvent(events, fn, function(event, fn) {
1201         var handler = parse(event)
1202         //保存fn,下面爲了處理mouseenter, mouseleave時,對fn進行了修改
1203         handler.fn = fn
1204         handler.sel = selector
1205         // 模仿 mouseenter, mouseleave
1206         if (handler.e in hover) fn = function(e) {
1207           /* 
1208             relatedTarget爲事件相關對象,只有在mouseover和mouseout事件時纔有值
1209             mouseover時表示的是鼠標移出的那個對象,mouseout時表示的是鼠標移入的那個對象
1210             當related不存在,表示事件不是mouseover或者mouseout,mouseover時!$.contains(this, related)當相關對象不在事件對象內
1211             且related !== this相關對象不是事件對象時,表示鼠標已經從事件對象外部移入到了對象自己,這個時間是要執行處理函數的
1212             當鼠標從事件對象上移入到子節點的時候related就等於this了,且!$.contains(this, related)也不成立,這個時間是不須要執行處理函數的
1213         */
1214           var related = e.relatedTarget
1215           if (!related || (related !== this && !$.contains(this, related))) return handler.fn.apply(this, arguments)
1216         }
1217         //事件委託
1218         handler.del = getDelegate && getDelegate(fn, event)
1219         var callback = handler.del || fn
1220         handler.proxy = function(e) {
1221           var result = callback.apply(element, [e].concat(e.data))
1222           //當事件處理函數返回false時,阻止默認操做和冒泡
1223           if (result === false) e.preventDefault(), e.stopPropagation()
1224           return result
1225         }
1226         //設置處理函數的在函數集中的位置
1227         handler.i = set.length
1228         //將函數存入函數集中
1229         set.push(handler)
1230         element.addEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
1231       })
1232   }
1233   //刪除綁定在元素上的指定類型的事件監聽函數,可同時刪除多種事件類型指定的函數,用數組或者還空格的字符串便可,同add
1234 
1235   function remove(element, events, fn, selector, capture) {
1236     var id = zid(element)
1237     eachEvent(events || '', fn, function(event, fn) {
1238       findHandlers(element, event, fn, selector).forEach(function(handler) {
1239         delete handlers[id][handler.i]
1240         element.removeEventListener(realEvent(handler.e), handler.proxy, eventCapture(handler, capture))
1241       })
1242     })
1243   }
1244 
1245   $.event = {
1246     add: add,
1247     remove: remove
1248   }
1249 
1250   //設置代理
1251   $.proxy = function(fn, context) {
1252     if ($.isFunction(fn)) {
1253       //若是fn是函數,則申明一個新的函數並用context做爲上下文調用fn
1254       var proxyFn = function() {
1255         return fn.apply(context, arguments)
1256       }
1257       //引用fn標示符
1258       proxyFn._zid = zid(fn)
1259       return proxyFn
1260     } else if (typeof context == 'string') {
1261       return $.proxy(fn[context], fn)
1262     } else {
1263       throw new TypeError("expected function")
1264     }
1265   }
1266 
1267   $.fn.bind = function(event, callback) {
1268     return this.each(function() {
1269       add(this, event, callback)
1270     })
1271   }
1272   $.fn.unbind = function(event, callback) {
1273     return this.each(function() {
1274       remove(this, event, callback)
1275     })
1276   }
1277   //綁定一次性事件監聽函數
1278   $.fn.one = function(event, callback) {
1279     return this.each(function(i, element) {
1280       //添加函數,而後在回調函數裏再刪除綁定。達到一次性事件的目的
1281       add(this, event, callback, null, function(fn, type) {
1282         return function() {
1283           var result = fn.apply(element, arguments) //這裏執行綁定的回調
1284           remove(element, type, fn) //刪除上面的綁定
1285           return result
1286         }
1287       })
1288     })
1289   }
1290 
1291   var returnTrue = function() {
1292     return true
1293   },
1294   returnFalse = function() {
1295     return false
1296   },
1297   ignoreProperties = /^([A-Z]|layer[XY]$)/,
1298     eventMethods = {
1299       preventDefault: 'isDefaultPrevented', //是否調用過preventDefault方法
1300       //取消執行其餘的事件處理函數並取消事件冒泡.若是同一個事件綁定了多個事件處理函數, 在其中一個事件處理函數中調用此方法後將不會繼續調用其餘的事件處理函數.
1301       stopImmediatePropagation: 'isImmediatePropagationStopped', //是否調用過stopImmediatePropagation方法,
1302       stopPropagation: 'isPropagationStopped' //是否調用過stopPropagation方法
1303     }
1304     //建立事件代理
1305 
1306     function createProxy(event) {
1307       var key, proxy = {
1308         originalEvent: event
1309       } //保存原始event
1310       for (key in event)
1311       if (!ignoreProperties.test(key) && event[key] !== undefined) proxy[key] = event[key] //複製event屬性至proxy
1312 
1313       //將preventDefault,stopImmediatePropagatio,stopPropagation方法定義到proxy上
1314       $.each(eventMethods, function(name, predicate) {
1315         proxy[name] = function() {
1316           this[predicate] = returnTrue
1317           return event[name].apply(event, arguments)
1318         }
1319         proxy[predicate] = returnFalse
1320       })
1321       return proxy
1322     }
1323 
1324     // emulates the 'defaultPrevented' property for browsers that have none
1325     //event.defaultPrevented返回一個布爾值,代表當前事件的默認動做是否被取消,也就是是否執行了 event.preventDefault()方法.
1326 
1327     function fix(event) {
1328       if (!('defaultPrevented' in event)) {
1329         event.defaultPrevented = false //初始值false
1330         var prevent = event.preventDefault // 引用默認preventDefault
1331         event.preventDefault = function() { //重寫preventDefault
1332           this.defaultPrevented = true
1333           prevent.call(this)
1334         }
1335       }
1336     }
1337     //事件委託
1338     $.fn.delegate = function(selector, event, callback) {
1339       return this.each(function(i, element) {
1340         add(element, event, callback, selector, function(fn) {
1341           return function(e) {
1342             //若是事件對象是element裏的元素,取與selector相匹配的
1343             var evt, match = $(e.target).closest(selector, element).get(0)
1344             if (match) {
1345               //evt成了一個擁有preventDefault,stopImmediatePropagatio,stopPropagation方法,currentTarge,liveFiredn屬性的對象,另也有e的默認屬性
1346               evt = $.extend(createProxy(e), {
1347                 currentTarget: match,
1348                 liveFired: element
1349               })
1350               return fn.apply(match, [evt].concat([].slice.call(arguments, 1)))
1351             }
1352           }
1353         })
1354       })
1355     }
1356     //取消事件委託
1357     $.fn.undelegate = function(selector, event, callback) {
1358       return this.each(function() {
1359         remove(this, event, callback, selector)
1360       })
1361     }
1362 
1363   $.fn.live = function(event, callback) {
1364     $(document.body).delegate(this.selector, event, callback)
1365     return this
1366   }
1367   $.fn.die = function(event, callback) {
1368     $(document.body).undelegate(this.selector, event, callback)
1369     return this
1370   }
1371 
1372   //on也有live和事件委託的效果,因此能夠只用on來綁定事件
1373   $.fn.on = function(event, selector, callback) {
1374     return !selector || $.isFunction(selector) ? this.bind(event, selector || callback) : this.delegate(selector, event, callback)
1375   }
1376   $.fn.off = function(event, selector, callback) {
1377     return !selector || $.isFunction(selector) ? this.unbind(event, selector || callback) : this.undelegate(selector, event, callback)
1378   }
1379   //主動觸發事件
1380   $.fn.trigger = function(event, data) {
1381     if (typeof event == 'string' || $.isPlainObject(event)) event = $.Event(event)
1382     fix(event)
1383     event.data = data
1384     return this.each(function() {
1385       // items in the collection might not be DOM elements
1386       // (todo: possibly support events on plain old objects)
1387       if ('dispatchEvent' in this) this.dispatchEvent(event)
1388     })
1389   }
1390 
1391   // triggers event handlers on current element just as if an event occurred,
1392   // doesn't trigger an actual event, doesn't bubble
1393   //觸發元素上綁定的指定類型的事件,可是不冒泡
1394   $.fn.triggerHandler = function(event, data) {
1395     var e, result
1396     this.each(function(i, element) {
1397       e = createProxy(typeof event == 'string' ? $.Event(event) : event)
1398       e.data = data
1399       e.target = element
1400       //遍歷元素上綁定的指定類型的事件處理函數集,按順序執行,若是執行過stopImmediatePropagation,
1401       //那麼e.isImmediatePropagationStopped()就會返回true,再外層函數返回false
1402       //注意each裏的回調函數指定返回false時,會跳出循環,這樣就達到的中止執行回面函數的目的
1403       $.each(findHandlers(element, event.type || event), function(i, handler) {
1404         result = handler.proxy(e)
1405         if (e.isImmediatePropagationStopped()) return false
1406       })
1407     })
1408     return result
1409   }
1410 
1411   // shortcut methods for `.bind(event, fn)` for each event type
1412   ;
1413   ('focusin focusout load resize scroll unload click dblclick ' +
1414     'mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave ' +
1415     'change select keydown keypress keyup error').split(' ').forEach(function(event) {
1416     $.fn[event] = function(callback) {
1417       return callback ?
1418       //若是有callback回調,則認爲它是綁定
1419       this.bind(event, callback) :
1420       //若是沒有callback回調,則讓它主動觸發
1421       this.trigger(event)
1422     }
1423   })
1424 
1425   ;
1426   ['focus', 'blur'].forEach(function(name) {
1427     $.fn[name] = function(callback) {
1428       if (callback) this.bind(name, callback)
1429       else this.each(function() {
1430         try {
1431           this[name]()
1432         } catch (e) {}
1433       })
1434       return this
1435     }
1436   })
1437 
1438   //根據參數建立一個event對象
1439   $.Event = function(type, props) {
1440     //當type是個對象時
1441     if (typeof type != 'string') props = type, type = props.type
1442     //建立一個event對象,若是是click,mouseover,mouseout時,建立的是MouseEvent,bubbles爲是否冒泡
1443     var event = document.createEvent(specialEvents[type] || 'Events'),
1444       bubbles = true
1445       //確保bubbles的值爲true或false,並將props參數的屬性擴展到新建立的event對象上
1446     if (props) for (var name in props)(name == 'bubbles') ? (bubbles = !! props[name]) : (event[name] = props[name])
1447     //初始化event對象,type爲事件類型,如click,bubbles爲是否冒泡,第三個參數表示是否能夠用preventDefault方法來取消默認操做
1448     event.initEvent(type, bubbles, true, null, null, null, null, null, null, null, null, null, null, null, null)
1449     //添加isDefaultPrevented方法,event.defaultPrevented返回一個布爾值,代表當前事件的默認動做是否被取消,也就是是否執行了 event.preventDefault()方法.
1450     event.isDefaultPrevented = function() {
1451       return this.defaultPrevented
1452     }
1453     return event
1454   }
1455 
1456 })(Zepto)
1457 
1458 /**
1459   Ajax處理部份
1460 **/
1461 ;
1462 (function($) {
1463   var jsonpID = 0,
1464     document = window.document,
1465     key,
1466     name,
1467     rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
1468     scriptTypeRE = /^(?:text|application)\/javascript/i,
1469     xmlTypeRE = /^(?:text|application)\/xml/i,
1470     jsonType = 'application/json',
1471     htmlType = 'text/html',
1472     blankRE = /^\s*$/
1473 
1474     // trigger a custom event and return false if it was cancelled
1475 
1476     function triggerAndReturn(context, eventName, data) {
1477       var event = $.Event(eventName)
1478       $(context).trigger(event, data)
1479       return !event.defaultPrevented
1480     }
1481 
1482     // trigger an Ajax "global" event
1483     //觸發 ajax的全局事件
1484 
1485     function triggerGlobal(settings, context, eventName, data) {
1486       if (settings.global) return triggerAndReturn(context || document, eventName, data)
1487     }
1488 
1489     // Number of active Ajax requests
1490     $.active = 0
1491 
1492     //settings.global爲true時表示須要觸發全局ajax事件
1493     //注意這裏的$.active++ === 0很巧妙,用它來判斷開始,由於只有$.active等於0時$.active++ === 0才成立
1494 
1495     function ajaxStart(settings) {
1496       if (settings.global && $.active++ === 0) triggerGlobal(settings, null, 'ajaxStart')
1497     }
1498     //注意這裏的 !(--$.active)同上面的殊途同歸,--$.active爲0,則表示$.active的值爲1,這樣用來判斷結束,也頗有意思
1499 
1500     function ajaxStop(settings) {
1501       if (settings.global && !(--$.active)) triggerGlobal(settings, null, 'ajaxStop')
1502     }
1503 
1504     // triggers an extra global event "ajaxBeforeSend" that's like "ajaxSend" but cancelable
1505     //觸發全局ajaxBeforeSend事件,若是返回false,則取消這次請求
1506 
1507     function ajaxBeforeSend(xhr, settings) {
1508       var context = settings.context
1509       if (settings.beforeSend.call(context, xhr, settings) === false || triggerGlobal(settings, context, 'ajaxBeforeSend', [xhr, settings]) === false) return false
1510 
1511       triggerGlobal(settings, context, 'ajaxSend', [xhr, settings])
1512     }
1513 
1514     function ajaxSuccess(data, xhr, settings) {
1515       var context = settings.context,
1516         status = 'success'
1517       settings.success.call(context, data, status, xhr)
1518       triggerGlobal(settings, context, 'ajaxSuccess', [xhr, settings, data])
1519       ajaxComplete(status, xhr, settings)
1520     }
1521     // type: "timeout", "error", "abort", "parsererror"
1522 
1523     function ajaxError(error, type, xhr, settings) {
1524       var context = settings.context
1525       settings.error.call(context, xhr, type, error)
1526       triggerGlobal(settings, context, 'ajaxError', [xhr, settings, error])
1527       ajaxComplete(type, xhr, settings)
1528     }
1529     // status: "success", "notmodified", "error", "timeout", "abort", "parsererror"
1530 
1531     function ajaxComplete(status, xhr, settings) {
1532       var context = settings.context
1533       settings.complete.call(context, xhr, status)
1534       triggerGlobal(settings, context, 'ajaxComplete', [xhr, settings])
1535       ajaxStop(settings)
1536     }
1537 
1538     // Empty function, used as default callback
1539 
1540     function empty() {}
1541     //可參考http://zh.wikipedia.org/zh-cn/JSONP
1542     $.ajaxJSONP = function(options) {
1543       if (!('type' in options)) return $.ajax(options)
1544 
1545       var callbackName = 'jsonp' + (++jsonpID), //建立回調函數名
1546         script = document.createElement('script'),
1547         //js文件加載完畢
1548         cleanup = function() {
1549           clearTimeout(abortTimeout) //清除下面的timeout事件處理
1550           $(script).remove() //移除建立的script標籤,由於該文件的JS內容已經解析過了
1551           delete window[callbackName] //清除掉指定的回調函數
1552         },
1553         //取消加載
1554         abort = function(type) {
1555           cleanup()
1556           // In case of manual abort or timeout, keep an empty function as callback
1557           // so that the SCRIPT tag that eventually loads won't result in an error.
1558           //這裏經過將回調函數從新賦值爲空函數來達到看似阻止加載JS的目的,實際上給script標籤設置了src屬性後,請求就已經產生了,而且不能中斷
1559           if (!type || type == 'timeout') window[callbackName] = empty
1560           ajaxError(null, type || 'abort', xhr, options)
1561         },
1562         xhr = {
1563           abort: abort
1564         }, abortTimeout
1565 
1566       if (ajaxBeforeSend(xhr, options) === false) {
1567         abort('abort')
1568         return false
1569       }
1570       //成功加載後的回調函數
1571       window[callbackName] = function(data) {
1572         cleanup()
1573         ajaxSuccess(data, xhr, options)
1574       }
1575 
1576       script.onerror = function() {
1577         abort('error')
1578       }
1579       //將回調函數名追加到請求地址,並賦給script,至此請求產生
1580       script.src = options.url.replace(/=\?/, '=' + callbackName)
1581       $('head').append(script)
1582 
1583       //若是設置了超時處理
1584       if (options.timeout > 0) abortTimeout = setTimeout(function() {
1585         abort('timeout')
1586       }, options.timeout)
1587 
1588       return xhr
1589     }
1590 
1591     //ajax全局設置
1592     $.ajaxSettings = {
1593       // Default type of request
1594       type: 'GET',
1595       // Callback that is executed before request
1596       beforeSend: empty,
1597       // Callback that is executed if the request succeeds
1598       success: empty,
1599       // Callback that is executed the the server drops error
1600       error: empty,
1601       // Callback that is executed on request complete (both: error and success)
1602       complete: empty,
1603       // The context for the callbacks
1604       context: null,
1605       // Whether to trigger "global" Ajax events
1606       global: true,
1607       // Transport
1608       xhr: function() {
1609         return new window.XMLHttpRequest()
1610       },
1611       // MIME types mapping
1612       accepts: {
1613         script: 'text/javascript, application/javascript',
1614         json: jsonType,
1615         xml: 'application/xml, text/xml',
1616         html: htmlType,
1617         text: 'text/plain'
1618       },
1619       // Whether the request is to another domain
1620       crossDomain: false,
1621       // Default timeout
1622       timeout: 0,
1623       // Whether data should be serialized to string
1624       processData: true,
1625       // Whether the browser should be allowed to cache GET responses
1626       cache: true
1627     };
1628 
1629   //根據MIME返回相應的數據類型,用做ajax參數裏的dataType用,設置預期返回的數據類型
1630   //如html,json,scirpt,xml,text
1631 
1632   function mimeToDataType(mime) {
1633     if (mime) mime = mime.split(';', 2)[0]
1634     return mime && (mime == htmlType ? 'html' : mime == jsonType ? 'json' : scriptTypeRE.test(mime) ? 'script' : xmlTypeRE.test(mime) && 'xml') || 'text'
1635   }
1636   //將查詢字符串追加到URL後面
1637 
1638   function appendQuery(url, query) {
1639     //注意這裏的replace,將第一個匹配到的&或者&&,&?,? ?& ??替換成?,用來保證地址的正確性
1640     return (url + '&' + query).replace(/[&?]{1,2}/, '?')
1641   }
1642 
1643   // serialize payload and append it to the URL for GET requests
1644   //序列化發送到服務器上的數據,若是是GET請求,則將序列化後的數據追加到請求地址後面
1645 
1646   function serializeData(options) {
1647     //options.processData表示對於非Get請求,是否自動將 options.data轉換爲字符串,前提是options.data不是字符串
1648     if (options.processData && options.data && $.type(options.data) != "string")
1649     //options.traditional表示是否以$.param方法序列化
1650     options.data = $.param(options.data, options.traditional)
1651     if (options.data && (!options.type || options.type.toUpperCase() == 'GET'))
1652     //若是是GET請求,將序列化後的數據追加到請求地址後面
1653     options.url = appendQuery(options.url, options.data)
1654   }
1655 
1656   $.ajax = function(options) {
1657     //注意這裏不能直接將$.ajaxSettings替換掉$.extend的第一個參數,這樣會改變 $.ajaxSettings裏面的值
1658     //這裏的作法是建立一個新對象
1659     var settings = $.extend({}, options || {})
1660     //若是它沒有定義$.ajaxSettings裏面的屬性的時候,纔去將$.ajaxSettings[key] 複製過來
1661     for (key in $.ajaxSettings) if (settings[key] === undefined) settings[key] = $.ajaxSettings[key]
1662     //執行全局ajaxStart
1663     ajaxStart(settings)
1664 
1665     //經過判斷請求地址和當前頁面地址的host是否相同來設置是跨域
1666     if (!settings.crossDomain) settings.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(settings.url) && RegExp.$2 != window.location.host
1667     //若是沒有設置請求地址,則取當前頁面地址
1668     if (!settings.url) settings.url = window.location.toString();
1669     //將data進行轉換
1670     serializeData(settings);
1671     //若是不設置緩存
1672     if (settings.cache === false) settings.url = appendQuery(settings.url, '_=' + Date.now())
1673 
1674     //若是請求的是jsonp,則將地址欄裏的=?替換爲callback=?,至關於一個簡寫
1675     var dataType = settings.dataType,
1676       hasPlaceholder = /=\?/.test(settings.url)
1677       if (dataType == 'jsonp' || hasPlaceholder) {
1678         if (!hasPlaceholder) settings.url = appendQuery(settings.url, 'callback=?')
1679         return $.ajaxJSONP(settings)
1680       }
1681 
1682     var mime = settings.accepts[dataType],
1683       baseHeaders = {},
1684       //若是請求地址沒有定請求協議,則與當前頁面協議相同
1685       protocol = /^([\w-]+:)\/\//.test(settings.url) ? RegExp.$1 : window.location.protocol,
1686       xhr = settings.xhr(),
1687       abortTimeout
1688       //若是沒有跨域
1689     if (!settings.crossDomain) baseHeaders['X-Requested-With'] = 'XMLHttpRequest'
1690     if (mime) {
1691       baseHeaders['Accept'] = mime
1692       if (mime.indexOf(',') > -1) mime = mime.split(',', 2)[0]
1693       xhr.overrideMimeType && xhr.overrideMimeType(mime)
1694     }
1695     //若是不是GET請求,設置發送信息至服務器時內容編碼類型
1696     if (settings.contentType || (settings.contentType !== false && settings.data && settings.type.toUpperCase() != 'GET')) baseHeaders['Content-Type'] = (settings.contentType || 'application/x-www-form-urlencoded')
1697     settings.headers = $.extend(baseHeaders, settings.headers || {})
1698 
1699     xhr.onreadystatechange = function() {
1700       if (xhr.readyState == 4) {
1701         xhr.onreadystatechange = empty;
1702         clearTimeout(abortTimeout)
1703         var result, error = false
1704         //根據狀態來判斷請求是否成功
1705         //狀態>=200 && < 300 表示成功
1706         //狀態 == 304 表示文件未改動過,也可認爲成功
1707         //若是是取要本地文件那也能夠認爲是成功的,xhr.status == 0是在直接打開頁面時發生請求時出現的狀態,也就是否是用localhost的形式訪問的頁面的狀況
1708         if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304 || (xhr.status == 0 && protocol == 'file:')) {
1709           //獲取返回的數據類型
1710           dataType = dataType || mimeToDataType(xhr.getResponseHeader('content-type'))
1711           result = xhr.responseText
1712 
1713           try {
1714             // http://perfectionkills.com/global-eval-what-are-the-options/
1715             if (dataType == 'script')(1, eval)(result) //若是返回的數據類型是JS
1716             else if (dataType == 'xml') result = xhr.responseXML
1717             else if (dataType == 'json') result = blankRE.test(result) ? null : $.parseJSON(result)
1718           } catch (e) {
1719             error = e
1720           }
1721           //若是解析出錯,則執行全局parsererror事件
1722           if (error) ajaxError(error, 'parsererror', xhr, settings)
1723           //不然執行ajaxSuccess
1724           else ajaxSuccess(result, xhr, settings)
1725         } else {
1726           //若是請求出錯,則根據xhr.status來執行相應的錯誤處理函數
1727           ajaxError(null, xhr.status ? 'error' : 'abort', xhr, settings)
1728         }
1729       }
1730     }
1731 
1732     var async = 'async' in settings ? settings.async : true
1733     xhr.open(settings.type, settings.url, async)
1734     //設置請求頭信息
1735     for (name in settings.headers) xhr.setRequestHeader(name, settings.headers[name])
1736 
1737     //若是ajaxBeforeSend函數返回的false,則取消這次請示
1738     if (ajaxBeforeSend(xhr, settings) === false) {
1739       xhr.abort()
1740       return false
1741     }
1742 
1743     //當設置了settings.timeout,則在超時後取消請求,並執行timeout事件處理函數
1744     if (settings.timeout > 0) abortTimeout = setTimeout(function() {
1745       xhr.onreadystatechange = empty
1746       xhr.abort()
1747       ajaxError(null, 'timeout', xhr, settings)
1748     }, settings.timeout)
1749 
1750     // avoid sending empty string (#319)
1751     xhr.send(settings.data ? settings.data : null)
1752     return xhr
1753   }
1754 
1755   // handle optional data/success arguments
1756   //將參數轉換成ajax函數指定的參數格式
1757 
1758   function parseArguments(url, data, success, dataType) {
1759     var hasData = !$.isFunction(data) //若是data是function,則認爲它是請求成功後的回調
1760     return {
1761       url: url,
1762       data: hasData ? data : undefined, //若是data不是function實例
1763       success: !hasData ? data : $.isFunction(success) ? success : undefined,
1764       dataType: hasData ? dataType || success : success
1765     }
1766   }
1767 
1768   //簡單的get請求
1769   $.get = function(url, data, success, dataType) {
1770     return $.ajax(parseArguments.apply(null, arguments))
1771   }
1772 
1773   $.post = function(url, data, success, dataType) {
1774     var options = parseArguments.apply(null, arguments)
1775     options.type = 'POST'
1776     return $.ajax(options)
1777   }
1778 
1779   $.getJSON = function(url, data, success) {
1780     var options = parseArguments.apply(null, arguments)
1781     options.dataType = 'json'
1782     return $.ajax(options)
1783   }
1784 
1785   //這裏的url能夠是http://www.xxxx.com selector這種形式,就是對加載進來的HTML對行一個篩選
1786   $.fn.load = function(url, data, success) {
1787     if (!this.length) return this
1788     //將請求地址用空格分開
1789     var self = this,
1790       parts = url.split(/\s/),
1791       selector,
1792       options = parseArguments(url, data, success),
1793       callback = options.success
1794     if (parts.length > 1) options.url = parts[0], selector = parts[1]
1795     //要對成功後的回調函數進行一個改寫,由於須要將加載進來的HTML添加進當前集合
1796     options.success = function(response) {
1797       //selector就是對請求到的數據就行一個篩選的條件,好比只獲取數據裏的類名爲.test的標籤
1798       self.html(selector ? $('<div>').html(response.replace(rscript, "")).find(selector) : response)
1799       //這裏纔是你寫的回調
1800       callback && callback.apply(self, arguments)
1801     }
1802     $.ajax(options)
1803     return this
1804   }
1805 
1806   var escape = encodeURIComponent
1807 
1808     function serialize(params, obj, traditional, scope) {
1809       var type, array = $.isArray(obj)
1810       $.each(obj, function(key, value) {
1811         type = $.type(value)
1812         //scope用做處理value也是object或者array的狀況
1813         //traditional表示是否以傳統的方式拼接數據,
1814         //傳統的意思就是好比現有一個數據{a:[1,2,3]},轉成查詢字符串後結果爲'a=1&a=2&a=3'
1815         //非傳統的的結果則是a[]=1&a[]=2&a[]=3
1816         if (scope) key = traditional ? scope : scope + '[' + (array ? '' : key) + ']'
1817         // handle data in serializeArray() format
1818         //當處理的數據爲[{},{},{}]這種狀況的時候,通常指的是序列化表單後的結果
1819         if (!scope && array) params.add(value.name, value.value)
1820         // recurse into nested objects
1821         //當value值是數組或者是對象且不是按傳統的方式序列化的時候,須要再次遍歷value
1822         else if (type == "array" || (!traditional && type == "object")) serialize(params, value, traditional, key)
1823         else params.add(key, value)
1824       })
1825     }
1826     //將obj轉換爲查詢字符串的格式,traditional表示是否轉換成傳統的方式,至於傳統的方式的意思看上面的註釋
1827     $.param = function(obj, traditional) {
1828       var params = []
1829       //注意這裏將add方法定到params,因此下面serialize時纔不須要返回數據
1830       params.add = function(k, v) {
1831         this.push(escape(k) + '=' + escape(v))
1832       }
1833       serialize(params, obj, traditional)
1834       return params.join('&').replace(/%20/g, '+')
1835     }
1836 })(Zepto)
1837 
1838 ;
1839 (function($) {
1840   //序列化表單,返回一個相似[{name:value},{name2:value2}]的數組
1841   $.fn.serializeArray = function() {
1842     var result = [],
1843       el
1844       //將集合中的第一個表單裏的全部表單元素轉成數組後進行遍歷
1845       $(Array.prototype.slice.call(this.get(0).elements)).each(function() {
1846         el = $(this)
1847         var type = el.attr('type')
1848         //判斷其type屬性,排除fieldset,submi,reset,button以及沒有被選中的radio和checkbox
1849         if (this.nodeName.toLowerCase() != 'fieldset' && !this.disabled && type != 'submit' && type != 'reset' && type != 'button' &&
1850         //注意這裏的寫法,當元素既不是radio也不是checkbox時,直接返回true,
1851         //當元素是radio或者checkbox時,會執行後面的this.checked,當radio或者checkbox被選中時this.checked獲得true值
1852         //這樣就能夠篩選中被選中的radio和checkbox了
1853         ((type != 'radio' && type != 'checkbox') || this.checked)) result.push({
1854           name: el.attr('name'),
1855           value: el.val()
1856         })
1857       })
1858       return result
1859   }
1860   //將表單的值轉成name1=value1&name2=value2的形式
1861   $.fn.serialize = function() {
1862     var result = []
1863     this.serializeArray().forEach(function(elm) {
1864       result.push(encodeURIComponent(elm.name) + '=' + encodeURIComponent(elm.value))
1865     })
1866     return result.join('&')
1867   }
1868   //表單提交
1869   $.fn.submit = function(callback) {
1870     if (callback) this.bind('submit', callback)
1871     else if (this.length) {
1872       var event = $.Event('submit')
1873       this.eq(0).trigger(event)
1874       if (!event.defaultPrevented) this.get(0).submit()
1875     }
1876     return this
1877   }
1878 
1879 })(Zepto)
1880 
1881 //CSS3動畫
1882 ;
1883 (function($, undefined) {
1884   var prefix = '',
1885     eventPrefix, endEventName, endAnimationName,
1886     vendors = {
1887       Webkit: 'webkit',
1888       Moz: '',
1889       O: 'o',
1890       ms: 'MS'
1891     },
1892     document = window.document,
1893     testEl = document.createElement('div'),
1894     supportedTransforms = /^((translate|rotate|scale)(X|Y|Z|3d)?|matrix(3d)?|perspective|skew(X|Y)?)$/i,
1895     transform,
1896     transitionProperty, transitionDuration, transitionTiming,
1897     animationName, animationDuration, animationTiming,
1898     cssReset = {}
1899     //將駝峯式的字符串轉成用-分隔的小寫形式,如borderWidth ==> border-width
1900 
1901     function dasherize(str) {
1902       return downcase(str.replace(/([a-z])([A-Z])/, '$1-$2'))
1903     }
1904 
1905     function downcase(str) {
1906       return str.toLowerCase()
1907     }
1908     //用於修正事件名
1909 
1910     function normalizeEvent(name) {
1911       return eventPrefix ? eventPrefix + name : downcase(name)
1912     }
1913 
1914     //根據瀏覽器的特性設置CSS屬性前輕輟和事件前輟,好比瀏覽器內核是webkit
1915     //那麼用於設置CSS屬性的前輟prefix就等於'-webkit-',用來修正事件名的前輟eventPrefix就是Webkit
1916     $.each(vendors, function(vendor, event) {
1917       if (testEl.style[vendor + 'TransitionProperty'] !== undefined) {
1918         prefix = '-' + downcase(vendor) + '-'
1919         eventPrefix = event
1920         return false
1921       }
1922     })
1923 
1924     transform = prefix + 'transform'
1925   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'] = ''
1926 
1927   $.fx = {
1928     off: (eventPrefix === undefined && testEl.style.transitionProperty === undefined),
1929     speeds: {
1930       _default: 400,
1931       fast: 200,
1932       slow: 600
1933     },
1934     cssPrefix: prefix,
1935     transitionEnd: normalizeEvent('TransitionEnd'),
1936     animationEnd: normalizeEvent('AnimationEnd')
1937   }
1938 
1939   $.fn.animate = function(properties, duration, ease, callback) {
1940     if ($.isPlainObject(duration)) ease = duration.easing, callback = duration.complete, duration = duration.duration
1941     //若是duration是數字時,表示動畫持續時間,若是是字符串,則從$.fx.speeds中取出相對應的值,若是沒有找到相應的值,對取默認值
1942     if (duration) duration = (typeof duration == 'number' ? duration : ($.fx.speeds[duration] || $.fx.speeds._default)) / 1000
1943     return this.anim(properties, duration, ease, callback)
1944   }
1945 
1946   $.fn.anim = function(properties, duration, ease, callback) {
1947     var key, cssValues = {}, cssProperties, transforms = '',
1948       that = this,
1949       wrappedCallback, endEvent = $.fx.transitionEnd
1950       //動畫持續時間默認值
1951     if (duration === undefined) duration = 0.4
1952     //若是瀏覽器不支持CSS3的動畫,則duration=0,意思就是直接跳轉最終值
1953     if ($.fx.off) duration = 0
1954 
1955     //若是properties是一個動畫名keyframe
1956     if (typeof properties == 'string') {
1957       // keyframe animation
1958       cssValues[animationName] = properties
1959       cssValues[animationDuration] = duration + 's'
1960       cssValues[animationTiming] = (ease || 'linear')
1961       endEvent = $.fx.animationEnd
1962     } else {
1963       cssProperties = []
1964       // CSS transitions
1965       for (key in properties)
1966       //若是設置 的CSS屬性是變形之類的
1967       if (supportedTransforms.test(key)) transforms += key + '(' + properties[key] + ') '
1968       else cssValues[key] = properties[key], cssProperties.push(dasherize(key))
1969 
1970       if (transforms) cssValues[transform] = transforms, cssProperties.push(transform)
1971       if (duration > 0 && typeof properties === 'object') {
1972         cssValues[transitionProperty] = cssProperties.join(', ')
1973         cssValues[transitionDuration] = duration + 's'
1974         cssValues[transitionTiming] = (ease || 'linear')
1975       }
1976     }
1977 
1978     wrappedCallback = function(event) {
1979       if (typeof event !== 'undefined') {
1980         if (event.target !== event.currentTarget) return // makes sure the event didn't bubble from "below"
1981         $(event.target).unbind(endEvent, wrappedCallback)
1982       }
1983       $(this).css(cssReset)
1984       callback && callback.call(this)
1985     }
1986     //當能夠執行動畫的時候,那麼動畫結束後會執行回調,
1987     //若是不支持持續動畫,在直接設置最終值後,不會執行動畫結束回調
1988     if (duration > 0) this.bind(endEvent, wrappedCallback)
1989 
1990     // trigger page reflow so new elements can animate
1991     this.size() && this.get(0).clientLeft
1992 
1993     //設置
1994     this.css(cssValues)
1995 
1996     //當持續時間小於等於0時,馬上還原
1997     if (duration <= 0) setTimeout(function() {
1998       that.each(function() {
1999         wrappedCallback.call(this)
2000       })
2001     }, 0)
2002 
2003     return this
2004   }
2005 
2006   testEl = null
2007 })(Zepto)
相關文章
相關標籤/搜索