/* 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)