var nodeHook, boolHook, rclass = /[ f]/g, rreturn = / /g, rfocusable = /^(?:input|select|textarea|button)$/i; jQuery.fn.extend({ attr: function( name, value ) { //遍历this //arguments.length > 1,jQuery.attr(this[i],name,value),返回this //arguments.length <= 1,jQuery.attr(this[i],name,value),返回this 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 ) { return this.each(function() { delete this[ jQuery.propFix[ name ] || name ]; }); }, //$('.div1').addClass('box2 box3'); addClass: function( value ) { var classes, elem, cur, clazz, j, i = 0, len = this.length, proceed = typeof value === "string" && value;//是字符串返回字符串,不是返回flase /* $('div').addClass( function(index){ alert(index); return 'box'+index; }); */ if ( jQuery.isFunction( value ) ) { console.log(this);//这里的this是jQuery对象,Object { 0: <div#div1.box>, 1: <div#div2.box>, 2: <div#div3.box>, length: 3},通过return ( context || rootjQuery ).find('.div1')原生方法获得,里面每一个是节点对象不是jQuery对象 return this.each(function( j ) { console.log(this);//这里不是jQuery对象是dom节点对象,<div id='div1'></div>,<div id='div2'></div>,<div id='div3'></div> console.log(jQuery( this ));//jQuery( this )是jQuery对象,Object { 0: <div#div1.box>, context: <div#div1.box>, length: 1 },Object { 0: <div#div2.box>, context: <div#div2.box>, length: 1 },Object { 0: <div#div3.box>, context: <div#div3.box>, length: 1 } /*jQuery( this )走的是 if ( selector.nodeType ) {//节点都有nodeType属性 this.context = this[0] = selector; this.length = 1; return this;*/ jQuery( this ).addClass( value.call( this, j, this.className ) ); }); } if ( proceed ) { // 把字符串正则分割成数组 classes = ( value || "" ).match( core_rnotwhite ) || []; for ( ; i < len; i++ ) { elem = this[ i ]; //不是元素节点返回false,elem.className元素有没有class属性,有就合并(重复不合并),cur是之前的class 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为true参数长度是0删除所有或者参数是字符串,为false传的不是字符串 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; //$('#div1').toggleClass('box2 box3',true);//有没有都是add //$('#div1').toggleClass('box2 box3',false);//有没有都是删除 if ( typeof stateVal === "boolean" && type === "string" ) {//真就添加,假就删除 return stateVal ? this.addClass( value ) : this.removeClass( value ); } if ( jQuery.isFunction( value ) ) { return this.each(function( i ) { jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); }); } //$('#div1').toggleClass('box2 box3') return this.each(function() { if ( type === "string" ) { // toggle individual class names var className, i = 0, self = jQuery( this ),//转成jQuery对象,hasClass是jQuery对象的方法。 //空格分割成数组 classNames = value.match( core_rnotwhite ) || []; while ( (className = classNames[ i++ ]) ) { // check each className given, space separated list if ( self.hasClass( className ) ) { self.removeClass( className ); } else { self.addClass( className ); } } // Toggle whole class name //$('#div1').toggleClass(false); } else if ( type === core_strundefined || type === "boolean" ) { if ( this.className ) { // store className if set data_priv.set( 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 ? "" : data_priv.get( 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 hooks, ret, isFunction, elem = this[0]; //$('#input1').val() if ( !arguments.length ) {//获取 if ( elem ) {//只获取第一个元素 //hooks兼容处理,jQuery.valHooks[ elem.type ]在valHooks 这个json中找不到就找jQuery.valHooks[ elem.nodeName.toLowerCase() ] hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; /* valHooks: { hooks = option: { //elem.type || elem.nodeName.toLowerCase() get: function( elem ) {} }, hooks = select: { get: function( elem ) {}, set: function( elem, value ) {} } 下面的: hooks = radio: { set: function( elem ) {} get: function( elem, value ) {} }, hooks = checkbox: { set: function( elem ) {}, get: function( elem, value ) {} } } */ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { return ret; } //不再hooks里面 ret = elem.value; return typeof ret === "string" ? // handle most common string cases ret.replace(rreturn, "") : // handle cases where value is null/undef or number ret == null ? "" : ret; } return; } //设置 isFunction = jQuery.isFunction( value ); return this.each(function( i ) { var val; if ( this.nodeType !== 1 ) { return; } if ( isFunction ) { val = value.call( this, i, jQuery( this ).val() ); } else { val = value; } // Treat null/undefined as ""; convert numbers to string if ( val == null ) {//$('#input1').val(null); val = ""; } else if ( typeof val === "number" ) {//$('#input1').val(123123); val += "";//转成字符串 } else if ( jQuery.isArray( val ) ) {//$('#input2').val(['hello']); val = jQuery.map(val, function ( value ) { return value == null ? "" : value + ""; }); } hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; // If set returns undefined, fall back to normal setting if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { this.value = val; } }); } }); jQuery.extend({//静态属性只能通过jQuery静态方式调 valHooks: {//option-get,select-get.select-set 兼容性处理 option: { get: function( elem ) { // attributes.value is undefined in Blackberry 4.7 but // uses .value. See #6932 var val = elem.attributes.value; //val不存在输出elem.value,val存在specified为false走elem.text return !val || val.specified ? elem.value : elem.text; } }, select: { //$('select').val() get: function( elem ) { var value, option, options = elem.options,//下拉选项 index = elem.selectedIndex,//当前索引值 //select只选了一个或者没有选,one为true,就是单选 one = elem.type === "select-one" || index < 0, //one为true时单选values是空,one是false时多选values是一个数组存储所有的选择的多个 values = one ? null : [], //单选时max是当前索引加1,多选时是下拉选项的长度 max = one ? index + 1 : options.length, // i = index < 0 ? max /*index < 0没有选择时one=true,i=max=0*/ :one ? index/*index >= 0有选择时,select-one单选one=true,i=index,max=index+1,*/ :0 /*index >= 0有选择时,不是select-one多选one=false,i=0,max=options.length*/ ; // 没有选择不进入循环,不获取select的val() //有选择单选,i=index,只获取index的val() //有选择好多选,全部获取 for ( ; i < max; i++ ) { option = options[ i ];//js对象 // IE6-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 ( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) && ( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) { // Get the specific value for the option value = jQuery( option ).val();//转成jQuery对象 // We don't need an array for one selects if ( one ) { return value; } // Multi-Selects return an array values.push( value ); } } return values; }, // $('#select').val(111);//111被选中了 set: function( elem, value ) { var optionSet, option, options = elem.options,//所有的下拉选项,js对象 values = jQuery.makeArray( value ),//转成数组 i = options.length; while ( i-- ) {//遍历 option = options[ i ]; if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {//在数组里面就把她设为选中 optionSet = true; } } // force browsers to behave consistently when non-matching value is set if ( !optionSet ) {//都没有 elem.selectedIndex = -1; } return values; } } }, attr: function( elem, name, value ) { var hooks, ret, nType = elem.nodeType; // 节点不存在,或者文本、属性、注释节点 if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { return; } // core_strundefined = typeof undefined, if ( typeof elem.getAttribute === core_strundefined ) { //$(document).attr('title','hello'); 走这里通过.设置 return jQuery.prop( elem, name, value ); } // 1是元素节点, if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { name = name.toLowerCase(); //只有type才做兼容性处理 hooks = jQuery.attrHooks[ name ] || //$('input').attr('checked',true);//没问题,做兼容了 ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook ); } if ( value !== undefined ) {//设置 //$('#div1').attr('miaov',null); 调用remove if ( value === null ) { jQuery.removeAttr( elem, name ); //hooks中,set方法存在,就调用set方法并且返回值存在,就返回返回值 } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret;//有兼容性执行兼容操作,返回值 } else {//没有兼容性操作设置值 elem.setAttribute( name, value + "" ); return value; } //hooks中,get方法存在,就调用get方法并且返回值存在,就返回返回值 } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {//获取,有兼容性返回值 return ret; } else {//获取没有兼容性时 ret = jQuery.find.attr( elem, name ); // Non-existent attributes return null, we normalize to undefined return ret == null ? undefined : ret; } }, removeAttr: function( elem, value ) { var name, propName, i = 0, //$('#div1').removeAttr('maio href id'); attrNames = value && value.match( core_rnotwhite );//core_rnotwhite = /S+/g, 非空格,返回数组 if ( attrNames && elem.nodeType === 1 ) { while ( (name = attrNames[i++]) ) { /* propFix: { "for": "htmlFor", "class": "className" }, */// $('#div1').removeAttr('class'); propName = jQuery.propFix[ name ] || name; // Boolean attributes get special treatment (#10870) if ( jQuery.expr.match.bool.test( name ) ) { // $('#div1').removeAttr('checked'); elem[ propName ] = false; } elem.removeAttribute( name );//调用原生 } } }, //hooks = jQuery.attrHooks[ name ] attrHooks: { type: {//只有name = 'type',才会有有兼容性判断。 set: function( elem, value ) {//只有set说明兼容只是针对设置没有获取 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 //当设置type = 'radio'时IE会有兼容性问题,所以要先设置类型才设置值 var val = elem.value; elem.setAttribute( "type", value ); if ( val ) { elem.value = val; } return value; } } } }, propFix: { "for": "htmlFor", "class": "className" }, prop: function( elem, name, value ) { var ret, hooks, notxml, nType = elem.nodeType; // don't get/set properties on text, comment and attribute nodes if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { return; } notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); if ( notxml ) { // Fix name and attach hooks name = jQuery.propFix[ name ] || name; hooks = jQuery.propHooks[ name ];//兼容性处理 } if ( value !== undefined ) {//设置值 return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ? ret : ( elem[ name ] = value );//prop使用的是.操作 } else {//获取值 return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ? ret : elem[ name ]; } }, propHooks: { tabIndex: {//光标切换顺序,只对tabIndex属性做兼容 get: function( elem ) {//只对get方法做兼容 return elem.hasAttribute( "tabindex" ) || rfocusable.test( elem.nodeName ) || elem.href ? elem.tabIndex : -1; } } } }); // Hooks for boolean attributes boolHook = { set: function( elem, value, name ) { if ( value === false ) { // Remove boolean attributes when set to false jQuery.removeAttr( elem, name ); } else { elem.setAttribute( name, name ); } return name; } }; jQuery.each( jQuery.expr.match.bool.source.match( /w+/g ), function( i, name ) { var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr; jQuery.expr.attrHandle[ name ] = function( elem, name, isXML ) { var fn = jQuery.expr.attrHandle[ name ], ret = isXML ? undefined : /* jshint eqeqeq: false */ // Temporarily disable this handler to check existence (jQuery.expr.attrHandle[ name ] = undefined) != getter( elem, name, isXML ) ? name.toLowerCase() : null; // Restore handler jQuery.expr.attrHandle[ name ] = fn; return ret; }; }); // Support: IE9+ // Selectedness for an option in an optgroup can be inaccurate if ( !jQuery.support.optSelected ) { jQuery.propHooks.selected = { get: function( elem ) { var parent = elem.parentNode; if ( parent && parent.parentNode ) { parent.parentNode.selectedIndex; } return null; } }; } jQuery.each([ "tabIndex", "readOnly", "maxLength", "cellSpacing", "cellPadding", "rowSpan", "colSpan", "useMap", "frameBorder", "contentEditable" ], function() { //value = callback.call( obj[ i ], i, obj[ i ] ); jQuery.propFix[ this.toLowerCase() ] = this; }); /* valHooks: { hooks = radio: { set: function( elem ) {} get: function( elem, value ) {} }, hooks = checkbox: { set: function( elem ) {}, get: function( elem, value ) {} } } */ jQuery.each([ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = { //$('#radio').val(['hello']); set: function( elem, value ) { if ( jQuery.isArray( value ) ) { return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );//设置选中状态 } } }; if ( !jQuery.support.checkOn ) { //有的话做处理,没有不做处理 //获取单选框和复选框的value值时绝大多数浏览器返回的都是on,有些是空的, jQuery.valHooks[ this ].get = function( elem ) { // Support: Webkit // "" is returned instead of "on" if a value isn't specified return elem.getAttribute("value") === null ? "on" : elem.value; }; } });