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

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

加油!

jquery源码解析:attr,prop,attrHooks,propHooks详解的更多相关文章

  1. gulp源码解析(一)—— Stream详解

    作为前端,我们常常会和 Stream 有着频繁的接触.比如使用 gulp 对项目进行构建的时候,我们会使用 gulp.src 接口将匹配到的文件转为 stream(流)的形式,再通过 .pipe() ...

  2. jQuery 源码分析(十八) ready事件详解

    ready事件是当DOM文档树加载完成后执行一个函数(不包含图片,css等),因此它的触发要早于load事件.用法: $(document).ready(fun) ;fun是一个函数,这样当DOM树加 ...

  3. jQuery 源码分析(十一) 队列模块 Queue详解

    队列是常用的数据结构之一,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行插入操作(入队).特点是先进先出,最先插入的元素最先被删除. 在jQuery内部,队列模块为动画模块提供基 ...

  4. Linux源码解析-内核栈与thread_info结构详解

    1.什么是进程的内核栈? 在内核态(比如应用进程执行系统调用)时,进程运行需要自己的堆栈信息(不是原用户空间中的栈),而是使用内核空间中的栈,这个栈就是进程的内核栈 2.进程的内核栈在计算机中是如何描 ...

  5. Spring源码解析--IOC根容器Beanfactory详解

    BeanFactory和FactoryBean的联系和区别 BeanFactory是整个Spring容器的根容器,里面描述了在所有的子类或子接口当中对容器的处理原则和职责,包括生命周期的一些约定. F ...

  6. AngularJS源码解析2:注入器的详解

    上一课,没有讲createInjector方法,只是讲了它的主要作用,这一课,详细来讲一下这个方法.此方法,最终返回的注册器实例对象有以下几个方法: invoke, instantiate, get, ...

  7. hanlp源码解析之中文分词算法详解

    词图 词图指的是句子中所有词可能构成的图.如果一个词A的下一个词可能是B的话,那么A和B之间具有一条路径E(A,B).一个词可能有多个后续,同时也可能有多个前驱,它们构成的图我称作词图. 需要稀疏2维 ...

  8. axios 源码解析(下) 拦截器的详解

    axios的除了初始化配置外,其它有用的应该就是拦截器了,拦截器分为请求拦截器和响应拦截器两种: 请求拦截器    ;在请求发送前进行一些操作,例如在每个请求体里加上token,统一做了处理如果以后要 ...

  9. jquery源码解析:代码结构分析

    本系列是针对jquery2.0.3版本进行的讲解.此版本不支持IE8及以下版本. (function(){ (21, 94)     定义了一些变量和函数,   jQuery = function() ...

  10. JQuery源码解析(一)

    写在前面:本<JQuery源码解析>系列是基于一些前辈们的文章进行进一步的分析.细化.修改而写出来的,在这边感谢那些慷慨提供科普文档的技术大拿们. 要查阅JQ的源文件请下载开发版的JQ.j ...

随机推荐

  1. Intellij IDEA 启动项目ClassNotFoundException

    博客原文地址:https://blog.csdn.net/wo541075754/article/details/45640267 使用Intellij IDEA  的过程中,新创建的项目启动时报错: ...

  2. java反射之ClassLoader

    类加载器ClassLoader ClassLoader能在运行时, 知道任意一个类的的所有属性和方法:对于任意一个对象,都能够调用它的任意方法和属性. 一.类加载器的工作机制 1:加载Jvm把clas ...

  3. jquery去掉click事件

    1:使用removeAttr( )和attr( ) $("a").attr("click",test()); $("a").removeAt ...

  4. jQuery的基础dom和css操作

    1.元素以及内容操作 $(function () { // alert($("a").html()); // 获取元素中间的html内容,包括标签和文本内容 // alert($( ...

  5. Golang 字符编码

    需要添加的库 go get code.google.com/p/go.text/encoding go get code.google.com/p/go.text/transform 两个转码函数 i ...

  6. ubuntu server静态IP和DNS服务器设置

    Ubuntu的网络参数保存在文件 /etc/network/interfaces中, 默认设置使用dhcp,动态IP获取.   设置静态ip的方法如下: 1) 编辑 /etc/network/inte ...

  7. [Training Video - 2] [Java Introduction] [Install Java and Eclipse, Create Class]

    Download Java : https://java.com/en/download/ Download Eclipse : https://www.eclipse.org/downloads/ ...

  8. MVC5数据库迁移命令!

    首先数据库迁移在上下文里设置要设置成为CreateDatabaseIfNotExists, 然后在Nuget控制平台输入命令 在“程序包管理器控制台”窗口中输入:Enable-Migrations   ...

  9. 11 Mortal Fibonacci Rabbits

    Problem Figure 4. A figure illustrating the propagation of Fibonacci's rabbits if they die after thr ...

  10. 获取iPod library中的媒体文件

    [获取iPod library中的媒体文件] The Media Player framework provides facilities for playing movie, music, audi ...