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)

 

posted on 2016-06-28 14:08  Lightt  阅读(637)  评论(0编辑  收藏  举报