Skip to main content
 首页 » 编程设计

jQuery之attr,prop与val方法源码分析

2022年07月16日26lvdongjie

这里只介绍这几个方法的源码,这部分引用了一个技巧,钩子对象,用来做兼容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

本文参考链接:https://www.cnblogs.com/webFrontDev/p/3189845.html