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

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

首先,看一下实例方法:

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

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

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

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

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

$("#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属性操作时,把名字写成了全部是小写的情况,这里做下兼容,使用户输入全部是小写属性名,也能正常操作。
});

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

加油!

上一篇:MongoDB导出-导入-迁移


下一篇:jxl(Java Excel API) 使用方法 【1】