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)