• jquery源码解析:attr,prop,attrHooks,propHooks详解


    我们先来看一下jQuery中有多少个方法是用来操作元素属性的。

    首先,看一下实例方法:

    然后,看下静态方法(工具方法):

    静态方法是内部使用的,我们外面使用的很少,实例方法才是对外的。

    接下来,我们来看下一些方法是如何使用的?

    $("#div1").attr("title","hello") ,设置属性,两个参数时。

    $("#div1").attr("title") , 获取属性值,一个参数时。

    $("#div1").prop("title"),也可以获得这个属性值。

    它们之间的区别就是:attr相当于原生的setAttribute(),getAttribute()。

    prop相当于原生的.属性名([属性名]),比如:div.title = "hello";div.title。

    在这里简单的讲一下原生的区别:

    $("#div1").attr("chaojidan","hello") ,给元素添加属性名为chaojidan的属性。在元素div标签上会显示chaojidan这个属性。

    $("#div1").prop("chaojidan","hello"),也是给元素添加属性名为chaojidan的属性,但是在元素div标签上不会显示这个属性。

    因为chaojidan是自定义属性,不是元素的固有属性。

    还有一个区别:

    <div chaojidan="hello" id="div1">

    $("#div1").attr("chaojidan") 返回hello。但是$("#div1").prop("chaojidan"),在有些浏览器下会返回空。因为chaojidan是自定属性。

    还有一个区别:

    对于a标签的href属性,attr返回href的属性值,但是prop返回document.URL + href的属性值。href是a标签的固有属性。

    更具体的区别,请看:http://www.cnblogs.com/chaojidan/p/4108777.html

    固有属性,如果你用removeProp删除的话,会有问题,比如,删除一个元素的id,是删除不掉的。但是用removeAttr就可以。因此prp和removeProp用的比较少,而attr和removeAttr用的比较多。

    最后,我们来看源码:

    jQuery.fn.extend({
      attr: function( name, value ) {
        return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); //此方法之前讲过,如果arguments.length > 1,就代表是设置操作,如果是false,那就代表是获取操作。而真正调用的回调方法是静态方法:jQuery.attr。name就是你传进来的属性名,value是你传进来的属性值。
      },

      removeAttr: function( name ) {
        return this.each(function() {
          jQuery.removeAttr( this, name );     //实例方法removeAttr,调用的也是同名的静态方法removeAttr。
        });
      },

      prop: function( name, value ) {   //也是静态方法jQuery.prop
        return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
      },

      removeProp: function( name ) {
        return this.each(function() {
          delete this[ jQuery.propFix[ name ] || name ];   //删除属性,如果属性名需要做兼容,就做兼容,比如:class要变成className。
        });
      },

      ......

    });

    jQuery.extend({

      attr: function( elem, name, value ) {
        var hooks, ret,
          nType = elem.nodeType;

        if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {  //元素不存在或文本节点,或注释节点,或属性节点,不能设置属性,直接返回。元素节点才能设置属性。
          return;
        }

        if ( typeof elem.getAttribute === core_strundefined ) {  //core_strundefined = "undefined"。document,window没有getAttribute方法,因此使用prop方法,而prop方法是用.属性名的形式。
          return jQuery.prop( elem, name, value );
        }

        if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {  //如果不是元素节点或元素节点是不是xml文档下的,如果是,那么jQuery.isXMLDoc( elem ) 返回true。这里的意思就是:如果是xml文档下的元素节点,就不会进入到if语句。xml文档下的元素都是自定义的,没有兼容性问题。所以不需要进入到if语句,进行兼容性处理。而html文档下的元素节点,有兼容性问题,所以需要做下处理。

          name = name.toLowerCase();

          //hooks是jQuery中专门用来解决兼容性问题的。support用来检测浏览器的兼容性,hooks来解决兼容性问题,hooks针对不同的类型有相对应的hooks,比如:attr,就对应于attrHooks。hooks分两种,一种是针对设置的兼容性处理,set方法,一种是针对获取的兼容性处理,get方法。如果有兼容性问题,set方法或get方法会返回兼容性处理之后的值,如果没有兼容性问题,set就会返回undefined,get就会返回null。大家可以看下attrHooks对象,其实传属性名进来,只有type属性才有兼容性问题。而且只针对设置操作,获取操作没有兼容性问题。具体一点就是:设置type = "radio" 的兼容性问题。

          hooks = jQuery.attrHooks[ name ] || ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );  //jQuery.expr = Sizzle.selectors,Sizzle.selectors对象中有match: matchExpr属性。matchExpr也是一个对象,它里面的bool属性值是:new RegExp( "^(?:" + booleans + ")$", "i" )。而booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped"。nodeHook = undefined。如果不是在IE下设置type类型为radio,那么就会判断name是否匹配此正则。如果匹配,就返回boolHook,而不匹配就会返回undefined。boolHook是用来专门处理bool类型属性的。比如:<input type="checkbox" checked="checked">, $("input").attr("checked") : checked,$("input").prop("checked") : true。checked属性就属于bool类型属性。针对以上这个例子,我们知道attr获取checked的值是checked,因为当我们设置是也应该$("input").attr("checked","checked"),但有些人可能对jQuery不熟,会写成$("input").attr("checked",true),那么这种写法行不行呢,也是可以的,因为jQueyr里面做了兼容处理。其实就是boolHook对象,大家可以在文章的最后看到这个对象,看它是如何处理的。

        }

        if ( value !== undefined ) {   //设置操作

          if ( value === null ) {  //$("#div1").attr("chaojidan",null)这种情况,会把chaojidan的属性移除。
            jQuery.removeAttr( elem, name );

          } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
            return ret;   //如果有兼容性问题,就进行处理,然后把处理的值返回。

          } else {
            elem.setAttribute( name, value + "" );   //用普通的方式,进行设置操作。因为属性值都是字符串,所以把number转化成字符串。
            return value;
          }

        } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { 
          return ret;   //获取操作。先看是否有get的兼容性操作。

        } else {
          ret = jQuery.find.attr( elem, name );  //jQuery.find = Sizzle。Sizzle中的attr方法对getAttribute 方法进行了兼容性处理。

          return ret == null ? undefined : ret;
        }
      }, 

      attrHooks: {
        type: {
          set: function( elem, value ) {    //这个方法是解决这样一个问题的:input = document.createElement("input");input.value = "t";input.type = "radio";support.radioValue = input.value === "t";当你先对input的value赋值,然后再设置input的type为radio时,IE下的input的value会变成on,而其他浏览器会得到t。

            if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {  //如果存在以上这个兼容性问题,也就是jQuery.support.radioValue =false,IE下是false,value就是你设置type属性的值,并且元素是input,意思就是:你对input元素设置type=radio的操作。
              var val = elem.value;    //怎么解决IE下的这个兼容性问题呢,我们先把这个input的value值保存起来。等设置了type = "radio" 后,再把值赋过去。这样它的input的value就不会变成on了。
              elem.setAttribute( "type", value );
              if ( val ) {
                elem.value = val;
              }
              return value;
            }
          }
        }
      },

      removeAttr: function( elem, value ) {
        var name, propName,
          i = 0,
            attrNames = value && value.match( core_rnotwhite );  //core_rnotwhite = /S+/g,这里的意思就是可以同时删除多个属性值,比如:$("div").removeAttr("id name class");,value = "id name class",调用match方法,并传入正则/S+/g,会返回[id,name,class]。

        if ( attrNames && elem.nodeType === 1 ) {   //必须是元素节点Element
          while ( (name = attrNames[i++]) ) {
            propName = jQuery.propFix[ name ] || name;  //propFix: {"for": "htmlFor","class":"className"},如果要删除的属性名是for或者class,那么需要做兼容处理。因此你做$("div").removeAttr("class")操作时,就不会出问题。

            if ( jQuery.expr.match.bool.test( name ) ) {  //如果要删除的属性名属于bool型的属性(也就是说它的值通过[属性名]获取时,是false或者true)
              elem[ propName ] = false;   //需要把此bool型的属性值赋为false,因为这个属性已经被移除了,不应该用[属性名]获取时,返回true。比如:input元素的checked属性,当你移除这个checked属性时,你通过input.checked获得true,那么就会被认为input中有这个checked,而这时checked你已经移除了,所以必须设置它的input.checked=false。具体请看这里:http://www.cnblogs.com/chaojidan/p/4108777.html

            }

            elem.removeAttribute( name );   //调用原生的方法移除掉
          }
        }
      },

      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 );

        if ( notxml ) {    //不是xml文档,需要做兼容处理
          name = jQuery.propFix[ name ] || name; //propFix上面已经讲了
          hooks = jQuery.propHooks[ name ];  //如果name是tabIndex,需要做兼容处理。tabIndex可以切换光标的顺序(通过tab键),按元素中tabIndex的属性值大小(1,2,3....),从小到大进行切换。
        }

        if ( value !== undefined ) {  //设置操作
          return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ? ret : ( elem[ name ] = value );    //这里如果返回的是elem[ name ] = value,其实是return value。

        } else {   //获取操作
          return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ? ret : elem[ name ];
        }
      }, 

      propHooks: {
        tabIndex: {    //此属性是在获取时,会有兼容性问题。其实就是在元素默认不支持tabIndex属性时,并且没有显式设置它的tabIndex属性时,IE6-8会返回0,而标准浏览器下会返回-1。所以兼容处理,都返回-1.
          get: function( elem ) {
            return elem.hasAttribute( "tabindex" ) || rfocusable.test( elem.nodeName ) || elem.href ? elem.tabIndex : -1;    //rfocusable = /^(?:input|select|textarea|button)$/i;,如果元素不属于正则中指定的这些元素时,并且元素没有href属性,那么就证明此元素默认不支持tabIndex属性。
          }
        }
      }

      ......
    });

    boolHook = {
      set: function( elem, value, name ) {    //根据例子,假设你设置$("input").attr("checked",true)
        if ( value === false ) {    //value等于true。如果是$("input").attr("checked",false)就移除checked的属性。
          jQuery.removeAttr( elem, name );   
        } else {
          elem.setAttribute( name, name );     //$("input").attr("checked","checked")
        }
        return name;
      }
    };

    if ( !jQuery.support.optSelected ) {   //这里的hooks是针对select元素的第一个option元素是否会默认被选中。
      jQuery.propHooks.selected = {   //在IE下(老版本safari),不会默认选中,因此获取option的selected值时返回false,而其他浏览器返回true。
        get: function( elem ) {    //只有get方法,因为只有获取时才会出现这个问题。假设你要获取option的selected属性值。
          var parent = elem.parentNode;   
          if ( parent && parent.parentNode ) {
            parent.parentNode.selectedIndex;  //只要在获取option的selected的值时,先访问select.selectedIndex属性,就可以设置option.selected = true了。意思就是在访问option的selected属性时,先访问其父级select元素的selectedIndex属性,强迫浏览器计算option的selected属性,以得到正确的值。需要注意的是option元素的父元素不一定是select,也有可能是optgroup。这里是支持IE9+,所以option的parentNode是optgroup,optgroup的parentNode是select。
          }
          return null;
        }
      };
    }

    jQuery.each([     //不懂each方法的,可以去这里看下http://www.cnblogs.com/chaojidan/p/4156600.html
      "tabIndex",
      "readOnly",
      "maxLength",
      "cellSpacing",
      "cellPadding",
      "rowSpan",
      "colSpan",
      "useMap",
      "frameBorder",
      "contentEditable"
      ], function() {
        jQuery.propFix[ this.toLowerCase() ] = this;    //这里的this就是数组中的选项,比如:jQuery.propFix[ tabIndex.toLowerCase() ] = tabIndex;之所以这样做,是以防有人做jQuery属性操作时,把名字写成了全部是小写的情况,这里做下兼容,使用户输入全部是小写属性名,也能正常操作。
    });

     这一课,还是比较重要的,牵涉的东西很多,想彻底弄明白,还是需要一定的基础的。

    加油!

  • 相关阅读:
    复制对象
    element.classList属性及方法应用
    Object.defineProperty
    965. Univalued Binary Tree
    700. Search in a Binary Search Tree
    561, Array Partition Ⅰ
    933. Number of Recent Calls
    999.Available Capture for rook
    961. N-Repeated Element in Size 2N Array
    709. To Lower Case
  • 原文地址:https://www.cnblogs.com/chaojidan/p/4192528.html
Copyright © 2020-2023  润新知