这里只介绍这几个方法的源码,这部分引用了一个技巧,钩子对象,用来做兼容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 names122 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 list130 state = isBool ? state : !self.hasClass(className);131 self[ state ? "addClass" : "removeClass" ](className);132 }133 134 // Toggle whole class name135 } else if (type === core_strundefined || type === "boolean") {136 if (this.className) {137 // store className if set138 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返回的不是undefined175 // 则返回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 // 否则返回ret186 return typeof ret === "string" ?187 // handle most common string cases188 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设置value339 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获取value347 // 在IE9+,Flash对象没有.getAttribute348 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设置为false375 if (!getSetAttribute && ruseDefault.test(name)) {376 elem[jQuery.camelCase('default-' + name)] = elem[propName] = false;377 } else {378 // 其他情况直接把propName属性设置为false379 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-9396 // Reset value to default in case type is set after value during creation397 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,说明是设置prop438 if (value !== undefined) {439 // 如果有钩子对象且存在set方法,440 // 返回非undefined的方法返回值,441 // 否则正常情况下直接用elem[name]设置prop442 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 attributes475 boolHook = {476 get: function (elem, name) {477 var478 // Use .prop to determine if this attribute is understood as boolean479 prop = jQuery.prop(elem, name),480 481 // Fetch it accordingly482 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 attributes488 // and conflates checked/selected into attroperties489 ruseDefault.test(name) ?490 elem[ jQuery.camelCase("default-" + name) ] :491 !!attr :492 493 // fetch an attribute node for properties not recognized as boolean494 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 false503 jQuery.removeAttr(elem, name);504 } else if (getSetInput && getSetAttribute || !ruseDefault.test(name)) {505 // IE<8 needs the *property* name506 elem.setAttribute(!getSetAttribute && jQuery.propFix[ name ] || name, name);507 508 // Use defaultChecked and defaultSelected for oldIE509 } else {510 elem[ jQuery.camelCase("default-" + name) ] = elem[ name ] = true;511 }512 513 return name;514 }515 };