zoukankan      html  css  js  c++  java
  • zepto源码注解

       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-zd])([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          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).*OSs([d_]+)/),
    1080       iphone = !ipad && ua.match(/(iPhonesOS)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(/(RIMsTabletsOS)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[^<]*(?:(?!</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)

    Zepto是一个轻量级的针对现代高级浏览器的JavaScript库, 它与jquery有着类似的api。 如果你会用jquery,那么你也会用zepto。这段时间公司的事情比较少,所以就把它的源码看了下,觉得写的挺好的,所以就有了给它写注释的想法。当然,这里面的注释只是我读代码时对它的理解,并不一定正确,如果有错误还请指正,先谢谢了。另外,敬请期待另一个JS大牛(果果)的JS库(then.js)的源码注释。

  • 相关阅读:
    自考过后的总结——如何快乐学习?
    自考总结——数据库原理第三章
    机房收费系统——用户权限和功能分析
    SQL视频总结
    学生信息管理系统总结——数据库的访问方式
    学习信息管理系统总结——数据库的连接和访问(一)
    学生信息管理系统总结——student数据库中表关系分析
    Kafka-文件管理
    Kafka-分区分配规则
    Kafka-处理请求(生产请求、获取请求)
  • 原文地址:https://www.cnblogs.com/qianduanjingying/p/5526645.html
Copyright © 2011-2022 走看看