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