七、属性操作Attributes

jQuery的属性操作模块由四部分组成:HTML属性操作、DOM属性操作、类样式操作和值操作。
  1.HTML属性操作指的是对文档中的属性进行读取、设置和移除操作,其中,属性名是小写的连字符式字符串,属性值也总是字符串。
    具体方法:
    .attr(name|pro|key,val|fn):设置或返回被选元素的属性值。
    .removeAttr(name):从每一个匹配的元素中删除一个属性
  2.DOM属性操作指的是对DOM元素的属性进行读取、设置和移除操作,其中,属性名是驼峰式字符串,属性值可以是任意类型。
    具体方法:
    .prop(n|p|k,v|f):获取在匹配的元素集中的第一个元素的属性值。
    .removeProp(name):用来删除由.prop()方法设置的属性集
  3.类样式操作指的是对DOM元素的属性className进行添加、移除操作。
    具体方法:
    .addClass(class|fn):为每个匹配的元素添加指定的类名。
    .removeClass([class|fn]):从所有匹配的元素中删除全部或者指定的类
    .toggleClass(class|fn[,sw]):如果存在(不存在)就删除(添加)一个类。
    hasClass(class):检查当前的元素是否含有某个特定的类,如果有,则返回true。
  4.值操作指的是对DOM元素的属性value进行读取、设置操作。
    具体方法:
    .val([val|fn|arr]):获得匹配元素的当前值。
var rnothtmlwhite = (/[^\x20\t\r\n\f]+/g);
var boolHook,
    attrHandle = jQuery.expr.attrHandle;

jQuery.fn.extend({
    // 方法.attr(name, value)用于获取匹配元素集合中第一个元素的HTML属性值,或者为匹配元素集合中的每个元素设置一个或多个HTML属性。该方法的功能取决于参数的个数和类型
    // 使用方法
    // .attr(name) 获取第一个匹配元素的HTML属性值
    // .attr(name,vlaue) 为每个匹配元素设置一个HTML属性
    // .attr(map) 为每个匹配元素设置一个或多个HTML属性
    // .attr(name, function(index, attr)) 为每个匹配元素设置一个HTML属性,属性值由传入的函数参数返回。如果函数没有返回值或返回undefined,则不改变当前属性值。该函数接受两个参数:当前元素在集合中的下标位置、当前HTML属性值。在该函数中,关键字this指向集合中的当前元素。
    // 方法.attr(name, vlaue)通过调用方法jQuery.access()和jQuery.attr()实现,方法jQuery.access()负责滴啊用方法jQuery.attr()获取第一个匹配元素的HTML属性值,或者为每个匹配元素设置一个或多个HTML属性,当属性值为函数时,还会执行并取其返回值作为新的属性值。
    attr: function (name, value) {
        return access(this, jQuery.attr, name, value, arguments.length > 1);
    },
    // 方法.removeAttr()用于从匹配元素集中的每个元素上移除一个或多个HTML属性,多个HTML属性之间用空格分隔。通过调用jQuery.removeAttr( this, name );实现。
    removeAttr: function (name) {
        return this.each(function () {
            jQuery.removeAttr(this, name);
        });
    }
});

jQuery.extend({
    // 方法jQuery.attr(elem, name, value)用于获取DOM元素的HTML属性值,或者为DOM元素设置一个HTML属性。该方法为方法.attr(name, value)提供了基础功能
    attr: function (elem, name, value) {
        // 参数elem: DOM元素,在其上读取、设置和移除指定的HTML属性。
        // 参数name: 要操作的HTML属性名。
        // 参数value: 要设置的HTML属性值。
        var ret, hooks,
            nType = elem.nodeType;

        // Don't get/set attributes on text, comment and attribute nodes
        // 忽略文本、注释、属性节点
        // 忽略文本节点(属性nodeType为3)、注释(属性nodeType为8)、属性节点(属性nodeType为2),不在这些节点上读取或设置HTML属性。
        if (nType === 3 || nType === 8 || nType === 2) {
            return;
        }

        // Fallback to prop when attributes are not supported
        // 如果不支持方法getAttribute()
        // 如果不支持方法getAttribute(),则读取对应的DOM属性。
        if (typeof elem.getAttribute === "undefined") {
            return jQuery.prop(elem, name, value);
        }

        // Attribute hooks are determined by the lowercase version
        // Grab necessary hook if one is defined
        // 转换HTML属性为小写,并获取对应的修正对象
        if (nType !== 1 || !jQuery.isXMLDoc(elem)) {
            // 依次尝试获取对应的特殊HTML属性修正对象jQuery.attrHooks[name.toLowerCase()]、布尔属性修正队形boolHook,或undefined
            hooks = jQuery.attrHooks[name.toLowerCase()] ||
                (jQuery.expr.match.bool.test(name) ? boolHook : undefined);
        }
        // 如果传入了参数value
        if (value !== undefined) {
            // 如果传入了参数value,但值是null,则移除HTML属性。
            if (value === null) {
                jQuery.removeAttr(elem, name);
                return;
            }
            // 如果传入了参数value,但值不是null,则设置HTML属性。
            if (hooks && "set" in hooks &&
                (ret = hooks.set(elem, value, name)) !== undefined) {
                // 优先调用对应的修正对象的修正方法set()。如果存在对应的修正对象,并且修正对象有修正方法set(),则调用该方法设置HTML属性;如果返回值不是undefined,则认为设置成功并返回。
                return ret;
            }
            // 否则调用原生方法setAttribute()设置HTML属性。注意第二个参数必须是字符串。
            elem.setAttribute(name, value + "");
            return value;
        }
        // 如果未传入参数value,则读取HTML属性
        if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) {
            // 优先调用对应的修正对象的修正方法get()。如果存在对应的修正对象,并且修正对象有修正方法get(),则调用该方法读取HTML属性;如果返回值不是null,则认为读取成功并返回。
            return ret;
        }

        // 否则调用原生方法attr()读取HTML属性。如果返回null,则统一转换成undefined,认为未定义该HTML属性。
        ret = jQuery.find.attr(elem, name);
        // Non-existent attributes return null, we normalize to undefined
        return ret == null ? undefined : ret;
    },
    // 特殊HTML属性修正对象集jQuery.attrHooks中存放了需要修正的HTML属性和对应的修正对象,修正对象含有修正方法set(elem, name),用于修正特殊HTML属性的设置方式。
    attrHooks: {
        // 如果测试项jQuery.support.radioValue为false,表示设置input元素的属性type为“radio”会导致属性value的值丢失,需要在设置之前先备份属性value的值,设置完成之后在恢复属性value为备份值。
        type: {
            set: function (elem, value) {
                if (!support.radioValue && value === "radio" &&
                    nodeName(elem, "input")) {
                    // 如果测试项jQuery.support.radioValue为false,则在设置input元素的属性type为“radio”之前先备份属性value的值,设置完成之后在恢复属性value为备份值。
                    var val = elem.value;
                    elem.setAttribute("type", value);
                    if (val) {
                        elem.value = val;
                    }
                    return value;
                }
            }
        }
    },
    // 方法jQuery.removeAttr(elem, value)用于从DOM元素上移除一个或多个HTML属性,多个HTML属性之间用空格分隔。该方法为方法.removeAttr(name, value)提供了基础功能,是对原生方法.removeAttribute()的封装和扩展
    removeAttr: function (elem, value) {
        // 参数elem:DOM元素,在其上移除HTML属性
        // 参数value:HTML属性,多个HTML属性之间用空格分隔。
        var name,
            i = 0,

            // Attribute names can contain non-HTML whitespace characters
            // 属性名可以包含非html空白字符
            // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
            attrNames = value && value.match(rnothtmlwhite);

        if (attrNames && elem.nodeType === 1) {
            // 如果elem是元素且存在HTML属性,开始遍历数组attrNames,逐个删除HTML属性
            while ((name = attrNames[i++])) {
                elem.removeAttribute(name);
            }
        }
    }
});

// Hooks for boolean attributes
// 如果某个HTML属性对应的DOM属性的值是布尔值,则称该HTML属性为布尔型HTML属性,其属性值是小写的属性名。
// 变量boolHook称为布尔型HTML属性修正对象,修正方法set(elem, value, name),用于修正布尔型HTML属性的设置方式。在方法boolHook.set(elem, value, name)中,则在设置HTML属性值前先同步更新对应的DOM属性值。
boolHook = {
    set: function (elem, value, name) {
        if (value === false) {
            // 如果参数value是false,则移除指定的HTML属性。
            // Remove boolean attributes when set to false
            jQuery.removeAttr(elem, name);
        } else {
            // 调用原生方法setAttribute(),设置HTML属性为小写属性名
            elem.setAttribute(name, name);
        }
        return name;
    }
};

jQuery.each(jQuery.expr.match.bool.source.match(/\w+/g), function (i, name) {
    var getter = attrHandle[name] || jQuery.find.attr;

    attrHandle[name] = function (elem, name, isXML) {
        var ret, handle,
            lowercaseName = name.toLowerCase();

        if (!isXML) {

            // Avoid an infinite loop by temporarily removing this function from the getter
            handle = attrHandle[lowercaseName];
            attrHandle[lowercaseName] = ret;
            ret = getter(elem, name, isXML) != null ?
                lowercaseName :
                null;
            attrHandle[lowercaseName] = handle;
        }
        return ret;
    };
});

var rfocusable = /^(?:input|select|textarea|button)$/i,
    rclickable = /^(?:a|area)$/i;

jQuery.fn.extend({
    // 方法.prop(name, value)用于获取匹配元素集合中第一个元素的DOM属性值,或者为匹配元素集合中的每个元素设置一个或多个DOM属性。该方法的功能取决于参数的个数和类型
    // 使用方法
    // .prop(name) 获取第一个匹配元素的DOM属性值
    // .prop(name,vlaue) 为每个匹配元素设置一个DOM属性
    // .prop(map) 为每个匹配元素设置一个或多个DOM属性
    // .prop(name, function(index, prop)) 为每个匹配元素设置一个DOM属性,属性值由传入的函数参数返回。如果函数没有返回值或返回undefined,则不改变当前属性值。该函数接受两个参数:当前元素在集合中的下标位置、当前DOM属性值。在该函数中,关键字this指向集合中的当前元素。
    // 方法.prop(name, vlaue)通过调用方法jQuery.access()和jQuery.prop()实现,方法jQuery.access()负责滴啊用方法jQuery.propr()获取第一个匹配元素的DOM属性值,或者为每个匹配元素设置一个或多个DOM属性,当属性值为函数时,还会执行并取其返回值作为新的属性值。
    prop: function (name, value) {
        return access(this, jQuery.prop, name, value, arguments.length > 1);
    },
    // 方法.removeProp(name)用于从匹配元素集合中的每个元素上移除一个DOM属性。该方法是对DOM元素原始删除(delete)操作的简单封装,并且解决了IE6-8不允许删除DOM属性的兼容性问题。
    removeProp: function (name) {
        return this.each(function () {
            // 遍历匹配元素集合,在每个匹配元素上移除指定的DOM属性
            delete this[jQuery.propFix[name] || name];
            // 使用运算符delete删除DOM元素上的属性
        });
    }
});

jQuery.extend({
    // 方法jQuery.prop( elem, name, value )用于获取DOM元素的DOM属性值,或者为DOM元素设置一个DOM属性。该方法为方法.prop(name, value)提供了基础功能,是对DOM属性原生读取、设置操作的封装和简化,并解决了少量浏览器兼容性问题。
    prop: function (elem, name, value) {
        // 参数elem:DOM元素,在其上读取、设置DOM属性。
        // 参数name:要操作的DOM属性名
        // 参数value:要操作的DOM属性值
        var ret, hooks,
            nType = elem.nodeType;

        // Don't get/set properties on text, comment and attribute nodes
        // 忽略文本、注释、属性节点,不在其上读取或设置DOM属性。
        if (nType === 3 || nType === 8 || nType === 2) {
            return;
        }
        // 转换DOM属性为驼峰式,获取对应的修正对象,DOM属性必须是驼峰式的
        if (nType !== 1 || !jQuery.isXMLDoc(elem)) {

            // Fix name and attach hooks
            name = jQuery.propFix[name] || name;
            hooks = jQuery.propHooks[name];
        }
        // 如果传入参数vlaue
        if (value !== undefined) {
            if (hooks && "set" in hooks &&
                (ret = hooks.set(elem, value, name)) !== undefined) {
                // 优先调用对应的修正对象的修正方法set()。如果存在对应的修正对象,并且修正对象有修正方法set(),则调用该方法设置DOM属性;如果返回值不是undefined,则认为设置成功并返回。
                return ret;
            }
            // 否则直接在DOM元素上设置DOM属性,并返回属性值
            return (elem[name] = value);
        }
        // 如果未传入参数value,则读取DOM属性
        if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) {
            // 优先调用对应的修正对象的修正方法get()。如果存在对应的修正对象,并且修正对象有修正方法get(),则调用该方法读取DOM属性;如果返回值不是null,则认为读取成功并返回。
            return ret;
        }
        // 否则直接从DOM元素上读取DOM属性
        return elem[name];
    },
    // 特殊DOM属性修正对象集jQuery.propHooks中存放了需要修正的DOM属性和对应的修正对象,修正对象含有修正方法get(elem, name),用于修正特殊HTML属性的读取方式。
    // 需要修正的DOM属性有:tabIndex
    propHooks: {
        tabIndex: {
            // 定义方法jQuery.propHookes.tabIndex.get(elem),如果明确指定了HTML属性tabIndex,则可以通过属性节点来读取;如果未指定,对于可以获得焦点或可以点击的元素,一律返回0,其他元素一律返回-1
            get: function (elem) {

                // Support: IE <=9 - 11 only
                // elem.tabIndex doesn't always return the
                // correct value when it hasn't been explicitly set
                // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
                // Use proper attribute retrieval(#12072)
                var tabindex = jQuery.find.attr(elem, "tabindex");

                if (tabindex) {
                    return parseInt(tabindex, 10);
                }

                if (
                    rfocusable.test(elem.nodeName) ||
                    rclickable.test(elem.nodeName) &&
                    elem.href
                ) {
                    return 0;
                }

                return -1;
            }
        }
    },

    propFix: {
        "for": "htmlFor",
        "class": "className"
    }
});

// Support: IE <=11 only
// Accessing the selectedIndex property
// forces the browser to respect setting selected
// on the option
// The getter ensures a default option is selected
// when in an optgroup
// eslint rule "no-unused-expressions" is disabled for this code
// since it considers such accessions noop
//仅支持:IE <=11 
//访问selectedIndex属性
//强制浏览器尊重所选的设置
//在选项上
// getter确保选择默认选项
//在optgroup中
//此代码禁用eslint规则“禁止使用表达式”
//因为它认为这种加入是行不通的
if (!support.optSelected) {
    // 如果测试项jQuery.support.optSelected为false,则DOm属性selected创建对应的修正方法jQuery.propHooks.selected.get(elem),在修正方法get(elem)中,会通过读取父元素select和父元素optgroup的属性selectedIndex来触发修正option元素的属性selected,最后返回null。而在方法jQuery.prop(elem, name, value)中,依然会读取elem['selected'],并返回。
    jQuery.propHooks.selected = {
        get: function (elem) {
            /* eslint no-unused-expressions: "off" */
            // 通过读取父元素select和父元素optgroup的属性selectedIndex来触发修正option元素的属性selected。
            var parent = elem.parentNode;
            if (parent && parent.parentNode) {
                parent.parentNode.selectedIndex;
            }
            return null;
        },
        set: function (elem) {

            /* eslint no-unused-expressions: "off" */

            var parent = elem.parentNode;
            if (parent) {
                parent.selectedIndex;

                if (parent.parentNode) {
                    parent.parentNode.selectedIndex;
                }
            }
        }
    };
}

jQuery.each([
    "tabIndex",
    "readOnly",
    "maxLength",
    "cellSpacing",
    "cellPadding",
    "rowSpan",
    "colSpan",
    "useMap",
    "frameBorder",
    "contentEditable"
], function () {
    jQuery.propFix[this.toLowerCase()] = this;
});




// Strip and collapse whitespace according to HTML spec
// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
function stripAndCollapse(value) {
    var tokens = value.match(rnothtmlwhite) || [];
    return tokens.join(" ");
}


function getClass(elem) {
    return elem.getAttribute && elem.getAttribute("class") || "";
}

function classesToArray(value) {
    if (Array.isArray(value)) {
        // 如果是数组直接返回
        return value;
    }
    if (typeof value === "string") {
        // 如果是字符串,用空白符分隔参数
        return value.match(rnothtmlwhite) || [];
    }
    return [];
}

jQuery.fn.extend({
    // 方法.addClass(name, value)用于获取匹配元素集合中的每个元素添加一个或多个类样式。该方法通过修改DOM属性className来修改类样式,其核心技巧是先在待添加的类样式和当前类样式的前后分别加空格,然后检查字符串方法indexOf()的返回值,来决定是添加还是忽略。
    // 使用方法
    // .addClass(className) 参数className是一个或多个以空格分隔的类样式,这些类样式将被添加到当前类样式之后。
    // .addClass(function(index, currentClass)) 参数是一个函数,返回一个或多个以空格分隔的类样式,这些类样式将被添加到当前类样式中。该函数接受2个参数:当前元素在集合中的下标位置、当前类样式。在该函数中,关键字this指向集合中的当前元素。

    addClass: function (value) {
        // 定义方法.addClass(value),参数vlaue可以是含有一个或多个类样式的字符串,类样式之间用空格分隔,或是返回类样式的函数
        var classes, elem, cur, curValue, clazz, j, finalValue,
            i = 0;

        if (isFunction(value)) {
            // 如果参数value是函数,则遍历匹配元素集合,在每个匹配元素上执行函数,并取其返回值作为待添加的类样式,然后迭代调用方法.addClass(className)添加类样式。
            return this.each(function (j) {
                jQuery(this).addClass(value.call(this, j, getClass(this)));
                // 执行函数时,指定关键字this指向当前元素,传入两个参数:当前元素在集合中的下标位置、当前样式值。
            });
        }
        // 调用classesToArray()方法对value类型进行判断,并格式化成数组格式。
        classes = classesToArray(value);

        if (classes.length) {
            // 遍历匹配元素集合,为每个匹配元素添加不存在的类样式
            while ((elem = this[i++])) {
                curValue = getClass(elem);
                cur = elem.nodeType === 1 && (" " + stripAndCollapse(curValue) + " ");

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

                    // Only assign if different to avoid unneeded rendering.
                    finalValue = stripAndCollapse(cur);
                    if (curValue !== finalValue) {
                        elem.setAttribute("class", finalValue);
                    }
                }
            }
        }

        return this;
    },
    // 方法.removeClass()用于重匹配元素集合中的每个元素移除一个或多个或全部类样式。该方法通过修改DOM属性className来移除类样式,其核心技巧是先在待移除的类样式和当前类样式的前后分别加空格,然后检查字符串方法replacef()逐个从当前类样式移除。
    // 使用方法
    // .removeClass(className) 参数className是一个或多个以空格分隔的类样式,这些类样式将从匹配元素的当前类样式中移除。
    // .removeClass(function(index, currentClass)) 参数是一个函数,返回一个或多个以空格分隔的类样式,这些类样式将从匹配元素的当前类样式中移除。该函数接受2个参数:当前元素在集合中的下标位置、当前类样式。在该函数中,关键字this指向集合中的当前元素。
    // .removeClass() 如果未传入参数,则移除匹配元素的所有类样式。
    removeClass: function (value) {
        var classes, elem, cur, curValue, clazz, j, finalValue,
            i = 0;
        // 如果参数value是函数,则遍历匹配元素集合,在每个匹配元素上执行函数,并取其返回值作为待移除的类演示,然后迭代调用方法.removeClass(value)移除类样式。
        if (isFunction(value)) {
            return this.each(function (j) {
                jQuery(this).removeClass(value.call(this, j, getClass(this)));
                // 在该函数中,关键字this指向集合中的当前元素。传入两个参数:当前元素在集合中的下标位置、当前类样式。
            });
        }
        // 如果未传入value,设置class属性为空
        if (!arguments.length) {
            return this.attr("class", "");
        }
        // 如果参数vlaue是字符串或数组
        classes = classesToArray(value);

        if (classes.length) {
            // 遍历匹配元素集合,从每个匹配元素上移除指定的类演示或全部类样式
            while ((elem = this[i++])) {
                curValue = getClass(elem);

                // This expression is here for better compressibility (see addClass)
                cur = elem.nodeType === 1 && (" " + stripAndCollapse(curValue) + " ");

                if (cur) {
                    j = 0;
                    while ((clazz = classes[j++])) {

                        // Remove *all* instances
                        while (cur.indexOf(" " + clazz + " ") > -1) {
                            cur = cur.replace(" " + clazz + " ", " ");
                        }
                    }

                    // Only assign if different to avoid unneeded rendering.
                    finalValue = stripAndCollapse(cur);
                    if (curValue !== finalValue) {
                        elem.setAttribute("class", finalValue);
                    }
                }
            }
        }

        return this;
    },
    // 方法.toggleClass([className],[switch])用于匹配元素集合中的每个元素添加或移除一个或多个或全部类样式,添加或移除的行为依赖于匹配元素是否含有指定的类样式以及参数switch的值。
    // 使用方法
    // .toggleClass(className) 切换一个或多个类样式。如果匹配元素含有指定的类样式,则移除,如果没有则添加。
    // .toggleClass(className, switch) 切换一个或多个类样式,由参数switch的值决定是添加还是移除类演示,如果是true则添加,如果是false则移除。
    // .toggleClass() 切换全部类样式。如果匹配元素含有类样式,则移除,如果没有则尝试恢复
    // .toggleClass(switch) 切换全部类样式,由参数switch的值决定是添加还是移除类演示,如果是false则总是移除,如果是true则检查匹配元素是否含有类样式,此时该方法等价于.toggleClass()。
    // .removeClass(function(index,class, [switch])) 待切换的类样式由一个函数确定,该函数接受3个参数:当前元素在集合中的下标位置、当前类样式和参数switch。在该函数中,关键字this指向集合中的当前元素。
    // .removeClass() 如果未传入参数,则移除匹配元素的所有类样式。
    toggleClass: function (value, stateVal) {
        var type = typeof value,
            isValidValue = type === "string" || Array.isArray(value);

        if (typeof stateVal === "boolean" && isValidValue) {
            return stateVal ? this.addClass(value) : this.removeClass(value);
        }
        // 如果参数value是函数,则遍历匹配元素集合,在每个匹配元素上执行函数,并取其返回值作为待切换的类样式,然后迭代调用.toggleClass(value, stateVal)切换类样式。
        if (isFunction(value)) {
            return this.each(function (i) {
                jQuery(this).toggleClass(
                    value.call(this, i, getClass(this), stateVal),
                    stateVal
                );
                // 接受3个参数:当前元素在集合中的下标位置、当前类样式和参数stateVal。在该函数中,关键字this指向集合中的当前元素。
            });
        }

        return this.each(function () {
            var className, i, self, classNames;
            // 如果参数value是字符串,调用方法.addClass(className)或.removeClass(className)为每个匹配元素添加或移除类样式。
            if (isValidValue) {

                // Toggle individual class names
                i = 0;
                self = jQuery(this);
                classNames = classesToArray(value);

                while ((className = classNames[i++])) {
                    // 遍历待切换的样式数组className,逐个添加或移除。如果函数存在类名,则移除,否则添加类名。
                    // Check each className given, space separated list
                    if (self.hasClass(className)) {
                        self.removeClass(className);
                    } else {
                        self.addClass(className);
                    }
                }

                // Toggle whole class name
            }
            // 遍历匹配元素集合时,如果未传入参数,或只传入了参数switch,则直接设置每个匹配元素的DOM属性className来切换全部类样式。
            else if (value === undefined || type === "boolean") {
                className = getClass(this);
                if (className) {
                    // 如果当前元素指定了类样式,则缓存下来,以便再次调用方法.toggle(switch)时恢复
                    // Store className if set
                    dataPriv.set(this, "__className__", 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.
                // 如果当前元素指定了类样式,或第一个参数是false,则移除所有类样式,否则恢复所有类样式。换句话说,如果第一个参数是布尔值true,则进行正常的切换,等价于没有传入参数的情况;如果第一个参数是false,则总是移除全部类样式。
                if (this.setAttribute) {
                    this.setAttribute("class",
                        className || value === false ?
                        "" :
                        dataPriv.get(this, "__className__") || ""
                    );
                }
            }
        });
    },
    // 方法.hasClass(selector)用于监测匹配元素集合中的任意是否含有指定的类样式。只要其中一个元素含有就返回true。该方法的核心技巧是在待查找的类样式和当前类样式的前后分别加空格,然后用字符串方法indexOf()检测。
    hasClass: function (selector) {
        var className, elem,
            i = 0;

        className = " " + selector + " ";
        // 遍历匹配元素集合,在当前元素的DOM属性className的前后加空格,并替换其中的换行符、制表符、回车符为空格,然后用字符串方法indexOf()检测其中是否含有指定的类样式。只要集合中的某个元素含有指定的类样式,就返回true。
        while ((elem = this[i++])) {
            if (elem.nodeType === 1 &&
                (" " + stripAndCollapse(getClass(elem)) + " ").indexOf(className) > -1) {
                return true;
            }
        }
        // 默认返回false
        return false;
    }
});

var rreturn = /\r/g;
// 匹配回车符(\r)

jQuery.fn.extend({
    // 方法.val(value)用于获取匹配元素集合中第一个元素的当前值,或者设置匹配元素集合中每个元素的值,主要用表单元素。该方法是对DOM属性value的封装和扩展,并解决了浏览器兼容性问题。
    // 使用方法:
    // .val() 获取第一个匹配元素的当前值。
    // .val(value) 设置每个匹配元素的值。
    // .val(function(index, value)) 设置每个匹配元素的值,由一个函数返回待设置的值。该函数接受2个参数:当前元素在集合中的下标位置、当前类样式。在该函数中,关键字this指向集合中的当前元素。
    val: function (value) {
        var hooks, ret, valueIsFunction,
            elem = this[0];
        // 如果未传入参数,则读取第一个匹配元素的当前值。
        if (!arguments.length) {
            if (elem) {
                // 查找节点名称或属性type对应的修正对象。
                hooks = jQuery.valHooks[elem.type] ||
                    jQuery.valHooks[elem.nodeName.toLowerCase()];
                // 优先调用对应修正对象的修正方法get()。如果存在对应的修正对象,并且修正对象有修正方法get(),则调用该方法读取当前值;如果返回值不是undefined,则认为读取成功并返回。
                if (hooks &&
                    "get" in hooks &&
                    (ret = hooks.get(elem, "value")) !== undefined
                ) {
                    return ret;
                }
                // 否则直接读取DOM属性value,对属性值简单处理后返回。如果属性值是字符串,则删除其中可能有的回车符(\r),然后返回;如果属性值是null或undefined,则返回空字符串;如果属性值是数值型,则直接返回。rreturn是匹配回车符的正则。
                ret = elem.value;

                // Handle most common string cases
                if (typeof ret === "string") {
                    return ret.replace(rreturn, "");
                }

                // Handle cases where value is null/undef or number
                return ret == null ? "" : ret;
            }

            return;
        }
        // 如果传入了参数,则设置每个匹配元素的值
        valueIsFunction = isFunction(value);
        // 遍历元素集合,设置每个匹配元素的值。设置时优先调用修正对象的修正方法set(),其次才会设置DOM属性vlaue。
        return this.each(function (i) {
            var val;

            if (this.nodeType !== 1) {
                return;
            }
            // 如果参数value是函数,则在每个匹配元素上执行函数,并取并返回值作为待设置的值。执行函数时,指定关键字this指向当前元素,传入两个参数:当前元素在集合中的下标位置、当前值。
            if (valueIsFunction) {
                val = value.call(this, i, jQuery(this).val());
            } else {
                val = value;
            }

            // Treat null/undefined as ""; convert numbers to string
            // 把null、undefined统一转换为空字符串,把数字转化字符串。如果变量val是数组,则遍历该数组,执行同样的转换。
            if (val == null) {
                val = "";

            } else if (typeof val === "number") {
                val += "";

            } else if (Array.isArray(val)) {
                val = jQuery.map(val, function (value) {
                    return value == null ? "" : value + "";
                });
            }
            // 查找节点名称或属性type对应的修正对象
            hooks = jQuery.valHooks[this.type] || jQuery.valHooks[this.nodeName.toLowerCase()];

            // If set returns undefined, fall back to normal setting
            // 优先调用对应的修正对象的修正方法set()。如果存在对应的修正对象,并且修正对象有修正方法set(),则调用该方法设置值;如果返回值不是undefined,则认为设置成功。否则直接设置DOM属性vlaue。
            if (!hooks || !("set" in hooks) || hooks.set(this, val, "value") === undefined) {
                this.value = val;
            }
        });
    }
});

jQuery.extend({
    // 值修正对象集jQuery.valHooks存放了需要修正或扩展的节点名称或节点类型和对应的修正对象,修正对象含有方法get(elem)或set(elem,value),分别用于修正DOM属性value的读取和设置方式。
    // 需要修正或扩展的节点名称或节点类型有:option、select、radio、checkbox
    valHooks: {
        // 对于option元素,如果指定了属性vlaue,则返回elem.vlaue,否则返回文本内容elem.text
        option: {
            get: function (elem) {

                var val = jQuery.find.attr(elem, "value");
                return val != null ?
                    val :

                    // Support: IE <=10 - 11 only
                    // option.text throws exceptions (#14686, #14858)
                    // Strip and collapse whitespace
                    // https://html.spec.whatwg.org/#strip-and-collapse-whitespace
                    stripAndCollapse(jQuery.text(elem));
            }
        },
        // 读取select元素的值时,如果该select元素是单选,则读取属性selectedIndex指定的option元素的值;如果是多选,则遍历所有option元素,则返回包含了所有选中的option元素值的数组
        // 设置select元素的值时,把参数value转换为数组,遍历所有option元素,如果某个option元素的值与数组中的某个值相等,则设置该option元素的属性selected为true,否则设置为false
        select: {
            get: function (elem) {
                var value, option, i,
                    options = elem.options,
                    index = elem.selectedIndex,
                    // 检查当前select元素是单选的还是多选
                    one = elem.type === "select-one",
                    values = one ? null : [],
                    // 如果是单选,则只读取属性selectedIndex指定option元素的值,如果是多选,则从头开始遍历所有的option元素
                    max = one ? index + 1 : options.length;
                if (index < 0) {
                    // 没有被选中
                    i = max;

                } else {
                    // 被选中
                    i = one ? index : 0;
                }

                // Loop through all the selected options
                // 遍历所有选择的选项
                for (; i < max; i++) {
                    option = options[i];

                    // Support: IE <=9 only
                    // IE8-9 doesn't update selected after form reset (#2551)
                    if ((option.selected || i === index) &&

                        // Don't return options that are disabled or in a disabled optgroup
                        !option.disabled &&
                        (!option.parentNode.disabled ||
                            !nodeName(option.parentNode, "optgroup"))) {

                        // Get the specific value for the option
                        value = jQuery(option).val();

                        // We don't need an array for one selects
                        if (one) {
                            return value;
                        }

                        // Multi-Selects return an array
                        values.push(value);
                    }
                }
                // 返回该数组
                return values;
            },

            set: function (elem, value) {
                var optionSet, option,
                    options = elem.options,
                    values = jQuery.makeArray(value),
                    i = options.length;
                // 遍历所有option元素,设置每个option元素的DOM属性selected
                while (i--) {
                    option = options[i];

                    /* eslint-disable no-cond-assign */

                    if (option.selected =
                        jQuery.inArray(jQuery.valHooks.option.get(option), values) > -1
                    ) {
                        optionSet = true;
                    }

                    /* eslint-enable no-cond-assign */
                }

                // Force browsers to behave consistently when non-matching value is set
                // 如果没有传入参数value,则修正select元素的DOM属性selectedIndex为-1
                if (!optionSet) {
                    elem.selectedIndex = -1;
                }
                return values;
            }
        }
    }
});
// Radios and checkboxes getter/setter
// 在属性操作模块初始化时,如果测试项jQuery.support.checkOn为false,即radio、checkbox元素的默认值不是“on”,则读取值时统一返回“on”
// 设置radio、checkbox元素的值时,如果值时数组,则检查当前元素的值与数组中的某个值是否相等,如果相等则设置当前元素的属性checked为true,否则设置为false
jQuery.each( [ "radio", "checkbox" ], function() {
    jQuery.valHooks[ this ] = {
        set: function( elem, value ) {
            if ( Array.isArray( value ) ) {
                return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
            }
        }
    };
    if ( !support.checkOn ) {
        jQuery.valHooks[ this ].get = function( elem ) {
            return elem.getAttribute( "value" ) === null ? "on" : elem.value;
        };
    }
} );

 

posted @ 2019-02-14 18:04  道鼎金刚  阅读(567)  评论(0)    收藏  举报