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     };
View Code

 

 

posted @ 2013-07-14 20:38  LukeLin  阅读(924)  评论(0编辑  收藏  举报