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