您的位置:首页 > Web前端 > JQuery

jQuery1.9.1--attr,prop与val方法源码分析

2013-07-14 20:38 841 查看
这里只介绍这几个方法的源码,这部分引用了一个技巧,钩子对象,用来做兼容fixed的对象,后面也有一些使用。钩子对象具体的兼容细节这里就不详解了。

var nodeHook, boolHook,
rclass = /[\t\r\n]/g,
rreturn = /\r/g,
rfocusable = /^(?:input|select|textarea|button|object)$/i,
rclickable = /^(?:a|area)$/i,
rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i,
ruseDefault = /^(?:checked|selected)$/i,
getSetAttribute = jQuery.support.getSetAttribute,
getSetInput = jQuery.support.input;

jQuery.fn.extend({
attr: function (name, value) {
return jQuery.access(this, jQuery.attr, name, value, arguments.length > 1);
},
removeAttr: function (name) {
return this.each(function () {
jQuery.removeAttr(this, name);
});
},
prop: function (name, value) {
return jQuery.access(this, jQuery.prop, name, value, arguments.length > 1);
},
removeProp: function (name) {
name = jQuery.propFix[ name ] || name;
return this.each(function () {
// try/catch handles cases where IE balks (such as removing a property on window)
try {
this[ name ] = undefined;
delete this[ name ];
} catch (e) {
}
});
},
addClass: function (value) {
var classes, elem, cur, clazz, j,
i = 0,
len = this.length,
proceed = typeof value === "string" && value;

if (jQuery.isFunction(value)) {
return this.each(function (j) {
jQuery(this).addClass(value.call(this, j, this.className));
});
}

if (proceed) {
// The disjunction here is for better compressibility (see removeClass)
classes = ( value || "" ).match(core_rnotwhite) || [];

for (; i < len; i++) {
elem = this[ i ];
cur = elem.nodeType === 1 && ( elem.className ?
( " " + elem.className + " " ).replace(rclass, " ") :
" "
);

if (cur) {
j = 0;
while ((clazz = classes[j++])) {
if (cur.indexOf(" " + clazz + " ") < 0) {
cur += clazz + " ";
}
}
elem.className = jQuery.trim(cur);

}
}
}

return this;
},
removeClass: function (value) {
var classes, elem, cur, clazz, j,
i = 0,
len = this.length,
proceed = arguments.length === 0 || typeof value === "string" && value;

if (jQuery.isFunction(value)) {
return this.each(function (j) {
jQuery(this).removeClass(value.call(this, j, this.className));
});
}
if (proceed) {
classes = ( value || "" ).match(core_rnotwhite) || [];

for (; i < len; i++) {
elem = this[ i ];
// This expression is here for better compressibility (see addClass)
cur = elem.nodeType === 1 && ( elem.className ?
( " " + elem.className + " " ).replace(rclass, " ") :
""
);

if (cur) {
j = 0;
while ((clazz = classes[j++])) {
// Remove *all* instances
while (cur.indexOf(" " + clazz + " ") >= 0) {
cur = cur.replace(" " + clazz + " ", " ");
}
}
elem.className = value ? jQuery.trim(cur) : "";
}
}
}

return this;
},
toggleClass: function (value, stateVal) {
var type = typeof value,
isBool = typeof stateVal === "boolean";

if (jQuery.isFunction(value)) {
return this.each(function (i) {
jQuery(this).toggleClass(value.call(this, i, this.className, stateVal), stateVal);
});
}

return this.each(function () {
if (type === "string") {
// toggle individual class names
var className,
i = 0,
self = jQuery(this),
state = stateVal,
classNames = value.match(core_rnotwhite) || [];

while ((className = classNames[ i++ ])) {
// check each className given, space separated list
state = isBool ? state : !self.hasClass(className);
self[ state ? "addClass" : "removeClass" ](className);
}

// Toggle whole class name
} else if (type === core_strundefined || type === "boolean") {
if (this.className) {
// store className if set
jQuery._data(this, "__className__", this.className);
}

// If the element has a class name or if we're passed "false",
// then remove the whole classname (if there was one, the above saved it).
// Otherwise bring back whatever was previously saved (if anything),
// falling back to the empty string if nothing was stored.
this.className = this.className || value === false ? "" : jQuery._data(this, "__className__") || "";
}
});
},
hasClass: function (selector) {
var className = " " + selector + " ",
i = 0,
l = this.length;
for (; i < l; i++) {
if (this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf(className) >= 0) {
return true;
}
}

return false;
},
val: function (value) {
var ret, hooks, isFunction,
// 获取伪数组中的第一个元素
elem = this[0];

// 如果没有传参,说明是获取value值
if (!arguments.length) {
if (elem) {
// 尝试获取valHooks钩子对象,
// 如果元素不具有type类型的钩子对象,
// 则尝试赋值元素标签键值的钩子对象
hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];

// 如果存在钩子对象且有get方法且get返回的不是undefined
// 则返回get方法的返回值
if (hooks && "get" in hooks && (ret = hooks.get(elem, "value")) !== undefined) {
return ret;
}

// 否则没有相应的钩子对象,直接获取元素的value值
ret = elem.value;

// 如果ret是字符串,返回过滤掉制表符的字符串,
// 否则ret为空就返回空字符串,
// 否则返回ret
return typeof ret === "string" ?
// handle most common string cases
ret.replace(rreturn, "") :
ret == null ? "" : ret;
}

return;
}

// 下面是有参数的情况,说明是设置value值

// 先判断value是否为函数
isFunction = jQuery.isFunction(value);

// 遍历元素集
return this.each(function (i) {
var val,
self = jQuery(this);

if (this.nodeType !== 1) {
return;
}

// 如果value是函数就执行,然后给ret赋值返回的值
if (isFunction) {
val = value.call(this, i, self.val());
} else {
val = value;
}

// 如果value为null或undefined,转化为字符串
// 如果是数字类型也转换为字符串
// 如果是数组类型,使用map方法返回一个返回值数组
if (val == null) {
val = "";
} else if (typeof val === "number") {
val += "";
} else if (jQuery.isArray(val)) {
val = jQuery.map(val, function (value) {
return value == null ? "" : value + "";
});
}

// 尝试获取钩子对象
hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];

// 如果没有钩子对象,或者钩子对象没有set方法,
// 又或者set方法返回的值是undefined,
// 就使用正常操作
if (!hooks || !("set" in hooks) || hooks.set(this, val, "value") === undefined) {
this.value = val;
}
});
}
});

jQuery.extend({
valHooks: {
option: {
/*
获取option的value值
*/
get: function (elem) {
// Blackberry 4.7的attributes.value为undefined但可以使用.value获取
var val = elem.attributes.value;
return !val || val.specified ? elem.value : elem.text;
}
},
/* 获取select的value值,如果是多选则返回数组 */
select: {
get: function (elem) {
var value, option,
options = elem.options,
index = elem.selectedIndex,
one = elem.type === 'select-one' || index < 0,
values = one ? null : [],
max = one ? index + 1 : options.length,
i = index < 0 ? max :
one ? index : 0;

// 遍历所有选中的项
for (; i < max; i++) {
option = options[i];

// 旧版本IE不会更新选中项当表单重置后
if ((option.selected || i === index) &&
// 不返回被禁用的选项或者在被禁用的optgroup中
(jQuery.support.optDisabled ? !option.disabled : option.getAttribute('disabled') === null) &&
(!option.parentNode.disabled || !jQuery.nodeName(option.parentNode, 'optgroup'))
) {
// 为option设置指定值
value = jQuery(option).val();

// 单选的话我们就不需要用数组了
if (one) {
return value;
}

// 多选就返回数组
values.push(value);
}
}

return values;
},
set: function (elem, value) {
var values = jQuery.makeArray(value);

jQuery(elem).find('option').each(function () {
this.selected = jQuery.inArray(jQuery(this).val(), values) >= 0;
});

if (!values.length) {
elem.selectedIndex = -1;
}
return values;
}
}
},
attr: function (elem, name, value) {
var hooks, notxml, ret,
nType = elem.nodeType;

// 如果elem的类型是文本,注释或者属性直接退出
if (!elem || nType === 3 || nType === 8 || nType === 2) {
return;
}

// 当不支持attributes时,回退用prop方法
if (typeof elem.getAttribute === core_strundefined) {
return jQuery.prop(elem, name, value);
}

// 是否非XML文档
notxml = nType !== 1 || !jQuery.isXMLDoc(elem);

// 如果钩子被定义了则抓取
if (notxml) {
name = name.toLowerCase();
// 如果不存在attrHooks钩子对象就尝试获取boolHook的钩子对象,
// 否则就用nodeHook这个钩子对象
hooks = jQuery.attrHooks[name] || (rboolean.test(name) ? boolHook : nodeHook);
}

if (value !== undefined) {
// value为null就删除attr属性
if (value === null) {
jQuery.removeAttr(elem, name);
} else if (hooks && notxml && 'set' in hooks && (ret = hooks.set(elem, value, name)) !== undefined) {
// 否则如果存在钩子方法,则返回set方法的返回值
return ret;
} else {
// 其他情况就直接用setAttribute设置value
elem.setAttribute(name, value + '');
}
} else if (hooks && notxml && 'get' in hooks && (ret = hooks.get(elem, name)) !== null) {
// 如果value是undefined,且存在钩子方法,
// 返回get方法的返回值
return ret;
} else {
// 其他情况(无钩子对象)就使用getAttribute获取value
// 在IE9+,Flash对象没有.getAttribute
if (typeof elem.getAttribute !== core_strundefined) {
ret = elem.getAttribute(name);

return ret == null ?
undefined :
ret;
}
}
},
removeAttr: function (elem, value) {
var name, propName,
i = 0,
// value值可以是空格连接的多个value,
// 这里通过正则匹配非空字符串,返回匹配的数组
attrNames = value && value.match(core_rnotwhite);

// 如果attrNames存在且elem是元素节点
if (attrNames && elem.nodeType === 1) {
// 遍历attrNames数组
while ((name = attrNames[i++])) {
// 如果没有propFix对象(将name转换为正确的字符串)就直接使用name作为属性值
propName = jQuery.propFix[name] || name;

// 布尔值的属性需要特殊处理
if (rboolean.test(name)) {
// 如果不支持获取和设置属性且有selected或checked属性,
// 则将defaultName和propName设置为false
if (!getSetAttribute && ruseDefault.test(name)) {
elem[jQuery.camelCase('default-' + name)] = elem[propName] = false;
} else {
// 其他情况直接把propName属性设置为false
elem[propName] = false;
}
} else {
// 非布尔值属性就调用jQuery.attr方法
jQuery.attr(elem, name, '');
}

// 删除元素上的该属性
elem.removeAttribute(getSetAttribute ? name : propName);
}
}
},
attrHooks: {
type: {
set: function (elem, value) {
if (!jQuery.support.radioValue && value === 'radio' && jQuery.nodeName(elem, 'input')) {
// Setting the type on a radio button after the value resets the value in IE6-9
// Reset value to default in case type is set after value during creation
var val = elem.value;
elem.setAttribute('type', value);
if (val) {
elem.value = val;
}
return value;
}
}
}
},
propFix: {
tabindex: 'tabIndex',
readonly: 'readOnly',
'for': 'htmlFor',
'class': 'className',
maxlength: 'maxLength',
cellspacing: 'cellSpacing',
cellpadding: 'cellPadding',
rowspan: 'rowSpan',
colspan: 'colSpan',
usemap: 'useMap',
frameborder: 'frameBorder',
contenteditable: 'contentEditable'
},
prop: function (elem, name, value) {
var ret, hooks, notxml,
nType = elem.nodeType;

if (!elem || nType === 3 || nType === 8 || nType === 2) {
return;
}

notxml = nType !== 1 || !jQuery.isXMLDoc(elem);

// 如果elem不是xml文档元素,获取被fixed的name和钩子对象
if (notxml) {
name = jQuery.propFix[name] || name;
hooks = jQuery.propHooks[name];
}

// 如果value不是undefined,说明是设置prop
if (value !== undefined) {
// 如果有钩子对象且存在set方法,
// 返回非undefined的方法返回值,
// 否则正常情况下直接用elem[name]设置prop
if (hooks && 'set' in hooks && (ret = hooks.set(elem, value, name)) !== undefined) {
return ret;
} else {
return (elem[name] = value);
}

// 如果value是undefined,说明是获取prop属性值
} else {
// 有钩子对象用其get方法,没有就用原生的方法
if (hooks && 'get' in hooks && (ret = hooks.get(elem, name)) !== null) {
return ret;
} else {
return elem[name];
}
}
},
propHooks: {
tabIndex: {
get: function (elem) {
// 当elem的tabindex没有被明确设置时,不会总返回正确的值
var attributeNode = elem.getAttributeNode('tabindex');

return attributeNode && attributeNode.specified ?
parseInt(attributeNode.value, 10) :
rfocusable.test(elem.nodeName) || rclickable.test(elem.nodeName) && elem.href ?
0 :
undefined;
}
}
}
});

// Hook for boolean attributes
boolHook = {
get: function (elem, name) {
var
// Use .prop to determine if this attribute is understood as boolean
prop = jQuery.prop(elem, name),

// Fetch it accordingly
attr = typeof prop === "boolean" && elem.getAttribute(name),
detail = typeof prop === "boolean" ?

getSetInput && getSetAttribute ?
attr != null :
// oldIE fabricates an empty string for missing boolean attributes
// and conflates checked/selected into attroperties
ruseDefault.test(name) ?
elem[ jQuery.camelCase("default-" + name) ] :
!!attr :

// fetch an attribute node for properties not recognized as boolean
elem.getAttributeNode(name);

return detail && detail.value !== false ?
name.toLowerCase() :
undefined;
},
set: function (elem, value, name) {
if (value === false) {
// Remove boolean attributes when set to false
jQuery.removeAttr(elem, name);
} else if (getSetInput && getSetAttribute || !ruseDefault.test(name)) {
// IE<8 needs the *property* name
elem.setAttribute(!getSetAttribute && jQuery.propFix[ name ] || name, name);

// Use defaultChecked and defaultSelected for oldIE
} else {
elem[ jQuery.camelCase("default-" + name) ] = elem[ name ] = true;
}

return name;
}
};


View Code
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: