jQuery1.9.1--attr,prop与val方法源码分析
这里只介绍这几个方法的源码,这部分引用了一个技巧,钩子对象,用来做兼容fixed的对象,后面也有一些使用。钩子对象具体的兼容细节这里就不详解了。
1 var nodeHook, boolHook, 2 rclass = /[\t\r\n]/g, 3 rreturn = /\r/g, 4 rfocusable = /^(?:input|select|textarea|button|object)$/i, 5 rclickable = /^(?:a|area)$/i, 6 rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i, 7 ruseDefault = /^(?:checked|selected)$/i, 8 getSetAttribute = jQuery.support.getSetAttribute, 9 getSetInput = jQuery.support.input; 10 11 jQuery.fn.extend({ 12 attr: function (name, value) { 13 return jQuery.access(this, jQuery.attr, name, value, arguments.length > 1); 14 }, 15 removeAttr: function (name) { 16 return this.each(function () { 17 jQuery.removeAttr(this, name); 18 }); 19 }, 20 prop: function (name, value) { 21 return jQuery.access(this, jQuery.prop, name, value, arguments.length > 1); 22 }, 23 removeProp: function (name) { 24 name = jQuery.propFix[ name ] || name; 25 return this.each(function () { 26 // try/catch handles cases where IE balks (such as removing a property on window) 27 try { 28 this[ name ] = undefined; 29 delete this[ name ]; 30 } catch (e) { 31 } 32 }); 33 }, 34 addClass: function (value) { 35 var classes, elem, cur, clazz, j, 36 i = 0, 37 len = this.length, 38 proceed = typeof value === "string" && value; 39 40 if (jQuery.isFunction(value)) { 41 return this.each(function (j) { 42 jQuery(this).addClass(value.call(this, j, this.className)); 43 }); 44 } 45 46 if (proceed) { 47 // The disjunction here is for better compressibility (see removeClass) 48 classes = ( value || "" ).match(core_rnotwhite) || []; 49 50 for (; i < len; i++) { 51 elem = this[ i ]; 52 cur = elem.nodeType === 1 && ( elem.className ? 53 ( " " + elem.className + " " ).replace(rclass, " ") : 54 " " 55 ); 56 57 if (cur) { 58 j = 0; 59 while ((clazz = classes[j++])) { 60 if (cur.indexOf(" " + clazz + " ") < 0) { 61 cur += clazz + " "; 62 } 63 } 64 elem.className = jQuery.trim(cur); 65 66 } 67 } 68 } 69 70 return this; 71 }, 72 removeClass: function (value) { 73 var classes, elem, cur, clazz, j, 74 i = 0, 75 len = this.length, 76 proceed = arguments.length === 0 || typeof value === "string" && value; 77 78 if (jQuery.isFunction(value)) { 79 return this.each(function (j) { 80 jQuery(this).removeClass(value.call(this, j, this.className)); 81 }); 82 } 83 if (proceed) { 84 classes = ( value || "" ).match(core_rnotwhite) || []; 85 86 for (; i < len; i++) { 87 elem = this[ i ]; 88 // This expression is here for better compressibility (see addClass) 89 cur = elem.nodeType === 1 && ( elem.className ? 90 ( " " + elem.className + " " ).replace(rclass, " ") : 91 "" 92 ); 93 94 if (cur) { 95 j = 0; 96 while ((clazz = classes[j++])) { 97 // Remove *all* instances 98 while (cur.indexOf(" " + clazz + " ") >= 0) { 99 cur = cur.replace(" " + clazz + " ", " "); 100 } 101 } 102 elem.className = value ? jQuery.trim(cur) : ""; 103 } 104 } 105 } 106 107 return this; 108 }, 109 toggleClass: function (value, stateVal) { 110 var type = typeof value, 111 isBool = typeof stateVal === "boolean"; 112 113 if (jQuery.isFunction(value)) { 114 return this.each(function (i) { 115 jQuery(this).toggleClass(value.call(this, i, this.className, stateVal), stateVal); 116 }); 117 } 118 119 return this.each(function () { 120 if (type === "string") { 121 // toggle individual class names 122 var className, 123 i = 0, 124 self = jQuery(this), 125 state = stateVal, 126 classNames = value.match(core_rnotwhite) || []; 127 128 while ((className = classNames[ i++ ])) { 129 // check each className given, space separated list 130 state = isBool ? state : !self.hasClass(className); 131 self[ state ? "addClass" : "removeClass" ](className); 132 } 133 134 // Toggle whole class name 135 } else if (type === core_strundefined || type === "boolean") { 136 if (this.className) { 137 // store className if set 138 jQuery._data(this, "__className__", this.className); 139 } 140 141 // If the element has a class name or if we're passed "false", 142 // then remove the whole classname (if there was one, the above saved it). 143 // Otherwise bring back whatever was previously saved (if anything), 144 // falling back to the empty string if nothing was stored. 145 this.className = this.className || value === false ? "" : jQuery._data(this, "__className__") || ""; 146 } 147 }); 148 }, 149 hasClass: function (selector) { 150 var className = " " + selector + " ", 151 i = 0, 152 l = this.length; 153 for (; i < l; i++) { 154 if (this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf(className) >= 0) { 155 return true; 156 } 157 } 158 159 return false; 160 }, 161 val: function (value) { 162 var ret, hooks, isFunction, 163 // 获取伪数组中的第一个元素 164 elem = this[0]; 165 166 // 如果没有传参,说明是获取value值 167 if (!arguments.length) { 168 if (elem) { 169 // 尝试获取valHooks钩子对象, 170 // 如果元素不具有type类型的钩子对象, 171 // 则尝试赋值元素标签键值的钩子对象 172 hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; 173 174 // 如果存在钩子对象且有get方法且get返回的不是undefined 175 // 则返回get方法的返回值 176 if (hooks && "get" in hooks && (ret = hooks.get(elem, "value")) !== undefined) { 177 return ret; 178 } 179 180 // 否则没有相应的钩子对象,直接获取元素的value值 181 ret = elem.value; 182 183 // 如果ret是字符串,返回过滤掉制表符的字符串, 184 // 否则ret为空就返回空字符串, 185 // 否则返回ret 186 return typeof ret === "string" ? 187 // handle most common string cases 188 ret.replace(rreturn, "") : 189 ret == null ? "" : ret; 190 } 191 192 return; 193 } 194 195 // 下面是有参数的情况,说明是设置value值 196 197 // 先判断value是否为函数 198 isFunction = jQuery.isFunction(value); 199 200 // 遍历元素集 201 return this.each(function (i) { 202 var val, 203 self = jQuery(this); 204 205 if (this.nodeType !== 1) { 206 return; 207 } 208 209 // 如果value是函数就执行,然后给ret赋值返回的值 210 if (isFunction) { 211 val = value.call(this, i, self.val()); 212 } else { 213 val = value; 214 } 215 216 // 如果value为null或undefined,转化为字符串 217 // 如果是数字类型也转换为字符串 218 // 如果是数组类型,使用map方法返回一个返回值数组 219 if (val == null) { 220 val = ""; 221 } else if (typeof val === "number") { 222 val += ""; 223 } else if (jQuery.isArray(val)) { 224 val = jQuery.map(val, function (value) { 225 return value == null ? "" : value + ""; 226 }); 227 } 228 229 // 尝试获取钩子对象 230 hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; 231 232 // 如果没有钩子对象,或者钩子对象没有set方法, 233 // 又或者set方法返回的值是undefined, 234 // 就使用正常操作 235 if (!hooks || !("set" in hooks) || hooks.set(this, val, "value") === undefined) { 236 this.value = val; 237 } 238 }); 239 } 240 }); 241 242 jQuery.extend({ 243 valHooks: { 244 option: { 245 /* 246 获取option的value值 247 */ 248 get: function (elem) { 249 // Blackberry 4.7的attributes.value为undefined但可以使用.value获取 250 var val = elem.attributes.value; 251 return !val || val.specified ? elem.value : elem.text; 252 } 253 }, 254 /* 获取select的value值,如果是多选则返回数组 */ 255 select: { 256 get: function (elem) { 257 var value, option, 258 options = elem.options, 259 index = elem.selectedIndex, 260 one = elem.type === 'select-one' || index < 0, 261 values = one ? null : [], 262 max = one ? index + 1 : options.length, 263 i = index < 0 ? max : 264 one ? index : 0; 265 266 // 遍历所有选中的项 267 for (; i < max; i++) { 268 option = options[i]; 269 270 // 旧版本IE不会更新选中项当表单重置后 271 if ((option.selected || i === index) && 272 // 不返回被禁用的选项或者在被禁用的optgroup中 273 (jQuery.support.optDisabled ? !option.disabled : option.getAttribute('disabled') === null) && 274 (!option.parentNode.disabled || !jQuery.nodeName(option.parentNode, 'optgroup')) 275 ) { 276 // 为option设置指定值 277 value = jQuery(option).val(); 278 279 // 单选的话我们就不需要用数组了 280 if (one) { 281 return value; 282 } 283 284 // 多选就返回数组 285 values.push(value); 286 } 287 } 288 289 return values; 290 }, 291 set: function (elem, value) { 292 var values = jQuery.makeArray(value); 293 294 jQuery(elem).find('option').each(function () { 295 this.selected = jQuery.inArray(jQuery(this).val(), values) >= 0; 296 }); 297 298 if (!values.length) { 299 elem.selectedIndex = -1; 300 } 301 return values; 302 } 303 } 304 }, 305 attr: function (elem, name, value) { 306 var hooks, notxml, ret, 307 nType = elem.nodeType; 308 309 // 如果elem的类型是文本,注释或者属性直接退出 310 if (!elem || nType === 3 || nType === 8 || nType === 2) { 311 return; 312 } 313 314 // 当不支持attributes时,回退用prop方法 315 if (typeof elem.getAttribute === core_strundefined) { 316 return jQuery.prop(elem, name, value); 317 } 318 319 // 是否非XML文档 320 notxml = nType !== 1 || !jQuery.isXMLDoc(elem); 321 322 // 如果钩子被定义了则抓取 323 if (notxml) { 324 name = name.toLowerCase(); 325 // 如果不存在attrHooks钩子对象就尝试获取boolHook的钩子对象, 326 // 否则就用nodeHook这个钩子对象 327 hooks = jQuery.attrHooks[name] || (rboolean.test(name) ? boolHook : nodeHook); 328 } 329 330 if (value !== undefined) { 331 // value为null就删除attr属性 332 if (value === null) { 333 jQuery.removeAttr(elem, name); 334 } else if (hooks && notxml && 'set' in hooks && (ret = hooks.set(elem, value, name)) !== undefined) { 335 // 否则如果存在钩子方法,则返回set方法的返回值 336 return ret; 337 } else { 338 // 其他情况就直接用setAttribute设置value 339 elem.setAttribute(name, value + ''); 340 } 341 } else if (hooks && notxml && 'get' in hooks && (ret = hooks.get(elem, name)) !== null) { 342 // 如果value是undefined,且存在钩子方法, 343 // 返回get方法的返回值 344 return ret; 345 } else { 346 // 其他情况(无钩子对象)就使用getAttribute获取value 347 // 在IE9+,Flash对象没有.getAttribute 348 if (typeof elem.getAttribute !== core_strundefined) { 349 ret = elem.getAttribute(name); 350 351 return ret == null ? 352 undefined : 353 ret; 354 } 355 } 356 }, 357 removeAttr: function (elem, value) { 358 var name, propName, 359 i = 0, 360 // value值可以是空格连接的多个value, 361 // 这里通过正则匹配非空字符串,返回匹配的数组 362 attrNames = value && value.match(core_rnotwhite); 363 364 // 如果attrNames存在且elem是元素节点 365 if (attrNames && elem.nodeType === 1) { 366 // 遍历attrNames数组 367 while ((name = attrNames[i++])) { 368 // 如果没有propFix对象(将name转换为正确的字符串)就直接使用name作为属性值 369 propName = jQuery.propFix[name] || name; 370 371 // 布尔值的属性需要特殊处理 372 if (rboolean.test(name)) { 373 // 如果不支持获取和设置属性且有selected或checked属性, 374 // 则将defaultName和propName设置为false 375 if (!getSetAttribute && ruseDefault.test(name)) { 376 elem[jQuery.camelCase('default-' + name)] = elem[propName] = false; 377 } else { 378 // 其他情况直接把propName属性设置为false 379 elem[propName] = false; 380 } 381 } else { 382 // 非布尔值属性就调用jQuery.attr方法 383 jQuery.attr(elem, name, ''); 384 } 385 386 // 删除元素上的该属性 387 elem.removeAttribute(getSetAttribute ? name : propName); 388 } 389 } 390 }, 391 attrHooks: { 392 type: { 393 set: function (elem, value) { 394 if (!jQuery.support.radioValue && value === 'radio' && jQuery.nodeName(elem, 'input')) { 395 // Setting the type on a radio button after the value resets the value in IE6-9 396 // Reset value to default in case type is set after value during creation 397 var val = elem.value; 398 elem.setAttribute('type', value); 399 if (val) { 400 elem.value = val; 401 } 402 return value; 403 } 404 } 405 } 406 }, 407 propFix: { 408 tabindex: 'tabIndex', 409 readonly: 'readOnly', 410 'for': 'htmlFor', 411 'class': 'className', 412 maxlength: 'maxLength', 413 cellspacing: 'cellSpacing', 414 cellpadding: 'cellPadding', 415 rowspan: 'rowSpan', 416 colspan: 'colSpan', 417 usemap: 'useMap', 418 frameborder: 'frameBorder', 419 contenteditable: 'contentEditable' 420 }, 421 prop: function (elem, name, value) { 422 var ret, hooks, notxml, 423 nType = elem.nodeType; 424 425 if (!elem || nType === 3 || nType === 8 || nType === 2) { 426 return; 427 } 428 429 notxml = nType !== 1 || !jQuery.isXMLDoc(elem); 430 431 // 如果elem不是xml文档元素,获取被fixed的name和钩子对象 432 if (notxml) { 433 name = jQuery.propFix[name] || name; 434 hooks = jQuery.propHooks[name]; 435 } 436 437 // 如果value不是undefined,说明是设置prop 438 if (value !== undefined) { 439 // 如果有钩子对象且存在set方法, 440 // 返回非undefined的方法返回值, 441 // 否则正常情况下直接用elem[name]设置prop 442 if (hooks && 'set' in hooks && (ret = hooks.set(elem, value, name)) !== undefined) { 443 return ret; 444 } else { 445 return (elem[name] = value); 446 } 447 448 // 如果value是undefined,说明是获取prop属性值 449 } else { 450 // 有钩子对象用其get方法,没有就用原生的方法 451 if (hooks && 'get' in hooks && (ret = hooks.get(elem, name)) !== null) { 452 return ret; 453 } else { 454 return elem[name]; 455 } 456 } 457 }, 458 propHooks: { 459 tabIndex: { 460 get: function (elem) { 461 // 当elem的tabindex没有被明确设置时,不会总返回正确的值 462 var attributeNode = elem.getAttributeNode('tabindex'); 463 464 return attributeNode && attributeNode.specified ? 465 parseInt(attributeNode.value, 10) : 466 rfocusable.test(elem.nodeName) || rclickable.test(elem.nodeName) && elem.href ? 467 0 : 468 undefined; 469 } 470 } 471 } 472 }); 473 474 // Hook for boolean attributes 475 boolHook = { 476 get: function (elem, name) { 477 var 478 // Use .prop to determine if this attribute is understood as boolean 479 prop = jQuery.prop(elem, name), 480 481 // Fetch it accordingly 482 attr = typeof prop === "boolean" && elem.getAttribute(name), 483 detail = typeof prop === "boolean" ? 484 485 getSetInput && getSetAttribute ? 486 attr != null : 487 // oldIE fabricates an empty string for missing boolean attributes 488 // and conflates checked/selected into attroperties 489 ruseDefault.test(name) ? 490 elem[ jQuery.camelCase("default-" + name) ] : 491 !!attr : 492 493 // fetch an attribute node for properties not recognized as boolean 494 elem.getAttributeNode(name); 495 496 return detail && detail.value !== false ? 497 name.toLowerCase() : 498 undefined; 499 }, 500 set: function (elem, value, name) { 501 if (value === false) { 502 // Remove boolean attributes when set to false 503 jQuery.removeAttr(elem, name); 504 } else if (getSetInput && getSetAttribute || !ruseDefault.test(name)) { 505 // IE<8 needs the *property* name 506 elem.setAttribute(!getSetAttribute && jQuery.propFix[ name ] || name, name); 507 508 // Use defaultChecked and defaultSelected for oldIE 509 } else { 510 elem[ jQuery.camelCase("default-" + name) ] = elem[ name ] = true; 511 } 512 513 return name; 514 } 515 };