jQuery源码分析系列:属性操作

属性操作

1.6.1相对1.5.x最大的改进,莫过于对属性.attr()的重写了。在1.6.1中,将.attr()一分为二: .attr()、.prop(),这是一个令人困惑的变更,也是一个破坏性的升级,会直接影响到无数的网站和项目升级到1.6。

简单的说,.attr()是通过setAttribute、getAttribute实现,.prop()则通过Element[ name ]实现:

jQuery.attr

setAttribute, getAttribute

jQuery.removeAttr

removeAttribute, removeAttributeNode(getAttributeNode )

jQuery.prop

Element[ name ]

jQuery.removeProp

delete Element[ name ]

 

事实上.attr()和.prop()的不同,是HTML属性(HTML attributes)和DOM属性(DOM properties)的不同。HTML属性解析的是HTML代码中的存在的属性,返回的总是字符串,而DOM属性解析的是DOM对象的属性,可能是字符串,也可能是一个对象,可能与HTML属性相同,也可能不同。

1 <a href="abc.html" class="csstest" style="font-size: 30px;">link</a>
2 
3 <input type="text" value="123">
4 
5 <input type="checkbox" checked="checked">

javascript代码:

 1 console.info( $('#a').attr('href') ); // abc.html
 2 
 3 console.info( $('#a').prop('href') ); // file:///H:/open/ws-nuysoft/com.jquery/jquery/abc.html
 4 
 5  
 6 console.info( $('#a').attr('class') ); // csstest
 7 console.info( $('#a').prop('class') ); // csstest
 8 
 9 console.info( document.getElementById('a').getAttribute('class') ); // csstest
10 
11 console.info( document.getElementById('a').className ); // csstest
12 
13 console.info( $('#a').attr('style') ); // font-size: 30px;
14 
15 console.info( $('#a').prop('style') ); // CSSStyleDeclaration { 0="font-size", fontSize="30px", ...}
16 
17 console.info( document.getElementById('a').getAttribute('style') ); // font-size: 30px;
18 
19 console.info( document.getElementById('a').style ); // CSSStyleDeclaration { 0="font-size", fontSize="30px", ...}
20 
21 console.info( $('#text').attr('value') ); // 123
22 console.info( $('#text').prop('value') ); // 123
23 
24 console.info( $('#checkbox').attr('checked') ); // checked
25 console.info( $('#checkbox').prop('checked') ); // true
26 
27  

不同之处总结如下:

  1.属性名可能不同,尽管大部分的属性名还是相似或一致的。

  2.HTML属性值总是返回字符串,DOM属性值则可能是整型、字符串、对象,可以获取更多的内容

  3.DOM属性总是返回当前的状态(值),而HTML属性(在大多数浏览)返回的初始化时的状态(值)

  4.DOM属性只能返回固定属性名的值,而HTML属性则可以返回在HTML代码中自定义的属性名的值

  5.相对于HTML属性的浏览器兼容问题,DOM属性名和属性值在浏览器之间的差异更小,并且DOM属性也有标准可依

优先使用.prop(),因为.prop()总是返回最新的状态(值)

从源码可以看出.attr()的处理过程,先特殊处理各种特殊情况,再用约定getAttribute()和setAttribute()方法

jQuery.attr()源码:

  .attr(attributeName) 取得第一个匹配元素的属性值(当属性没有被设置时,返回undefined,不能用在文本节点、注释节点、属性节点上)

  .attr(attributeName, value) 设置单个属性

  .attr(map) 设置多个属性

  .attr(attributeName, function(index, attr)) 通过函数的返回值设置属

 1     //工具方法  设置或获取HTML元素  setAttribute和getAttribute实现        
 2     attr: function( elem, name, value, pass ) {
 3             var nType = elem.nodeType;
 4             //节点类型不能是:文本 注释 属性节点
 5             if(!elem || nType === 3 || nType === 8 nType === 2){
 6                 return undefined;
 7             }
 8             //1>>遇到与方法同名的属性 则执行方法
 9             //2>>遇到扩展或需要修正的属性 执行相应的方法
10             //判断属性名是否在jQuery提供的方法jQuery.attrFn中,是则直接调用方法。
11             if(pass && name in jQuery.attrFn){//属性方法
12                 return jQuery(elem)[name](value);
13             }
14             //如果不支持getAttribute 则调用$.prop()方法
15             if(!("getAttribute" in elem)){
16                 return jQuery.prop(elem,name,value);
17             }
18             var ret,hooks,
19                 notxml = nType !==1 || !jQuery.isXMLDoc(elem);//判断documentElement是否存在
20 
21             //格式化name      attrFix: { tabindex: "tabIndex"}
22             name = notxml && jQuery.attrFix[name] || name;
23             //属性钩子:type:  tabIndex
24             hooks = jQuery.attrHooks[name];
25             //如果没有name对应的钩子
26             if(!hooks){
27                 //使用boolean钩子处理boolean属性
28                 (typeof value === "boolean" || value === undefined || value.tolowerCase() === name.toLowerCase())){
29                     //使用布尔钩子(静态方法对象):set get
30                     hooks = boolHook;
31                 //使用表单钩子
32                 }else if (formHook && (jQuery.nodeName(elem,"form") || rinvalidChar.test(name))){
33                 //使用表单钩子(静态方法对象):set get
34                 hooks = formHook;
35                 }
36             }
37 
38             //定义了value   设置或删除
39             if(value !== undefined){
40             /*
41                 typeof null === 'object'   true
42                 typeof undefined === 'undefined'  true
43                 null == undefined   true
44                 null === undefined  false
45             */
46                 if(value === null){//有值但是为空 即将值设置为空
47                     jQuery.reomveAttr(elem,name);
48                     return undefined;
49                 //属性钩子 布尔钩子 表单钩子 如果有对象的钩子 就调用set方法
50                 }else if(hooks && "set" in hooks && noxml && (ret = hooks.set(elem,value,name))!==undefeind){
51                     return ret;
52                 }else{
53                     //调用setAttribute方法
54                     elem.setAttribute(name,"" + value);
55                     return value;
56                 }
57             //value是undefined,说明去属性 ,存在对应钩子有get方法,调用钩子的get方法
58             }else if(hooks && "get" in hooks && notxml){
59                 return hooks.get(elem,name);
60             }else{
61                 //取属性值
62                 ret = elem.getAttribute(name);
63                 //属性不存返回null  格式化为undefined
64                 return ret === null ? undefined : ret;
65             }
66         },

其中调用的钩子有些不明白的地方:

attrHooks源码:如果name = type属性,则加判断后调用setAttribute设置属性值,或者name = tabIndex时通过getAttributeNode("tabIndex")获得//获得自定义对象属性.

    //属性钩子 name = type || tabIndex
    attrHooks: {
        type: {
            set: function( elem, value ) {
                //type属性在IE下不能改变 如果改变报错
                if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
                    jQuery.error( "type property can't be changed" );
                } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
                    var val = elem.value;
                    elem.setAttribute( "type", value );//最后调用setAttribute设置type属性的值
                    if ( val ) {
                        elem.value = val;
                    }
                    return value;
                }
            }
        },
        tabIndex: {
            get: function( elem ) {
                //获得tabIndex的值
                var attributeNode = elem.getAttributeNode("tabIndex");//获得自定义对象属性
                return attributeNode && attributeNode.specified ?
                    parseInt( attributeNode.value, 10 ) ://获得自定义对象属性的值
                    rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
                        0 :
                        undefined;
            }
        }
    },

AttrFn源码:如果name在这个属性方法的对象中,直接调用这个属性方法。

      //取属性用到的方法
      attrFn: {
        val: true,
        css: true,
        html: true,
        text: true,
        data: true,
         true,
        height: true,
        offset: true
    },

boolHook源码:

 1     //bool钩子
 2     boolHook = {
 3         get: function( elem, name ) {
 4             // Align boolean attributes with corresponding properties
 5             return elem[ jQuery.propFix[ name ] || name ] ?
 6                 name.toLowerCase() :
 7                 undefined;
 8         },
 9         set: function( elem, value, name ) {
10             var propName;
11             if ( value === false ) {//value不存在时,删除boolean属性
12                 jQuery.removeAttr( elem, name );
13             } else {
14                 //属性名 是否在propFix中,存在就直接调用
15                 propName = jQuery.propFix[ name ] || name;
16                 if ( propName in elem ) {
17                     elem[ propName ] = value;
18                 }
19                 //propName不在elem中就用setAttribute
20                 elem.setAttribute( name, name.toLowerCase() );
21             }
22             return name;
23         }
24     };

formHook源码:设置表单钩子,get/set。

$.prop()源码:

 1         //获取DOM属性
 2         prop: function( elem, name, value ) {
 3             var nType = elem.nodeType;
 4             //节点类型不能是:文本 注释 属性节点
 5             if(!elem || nType === 3 || nType === 8 nType === 2){
 6                 return undefined;
 7             }
 8             var ret,hooks,
 9                 notxml = nType !==1 || !jQuery.isXMLDoc(elem);//判断documentElement是否存在
10 
11             //格式化name      attrFix: { tabindex: "tabIndex"}
12             name = notxml && jQuery.attrFix[name] || name;
13             //属性钩子:name = type || tabIndex
14             hooks = jQuery.attrHooks[name];
15             if(value !== undefined){
16                 //如果钩子存在set  调用set方法
17                 if(hooks && "set" && (ret = hooks.set(elem,value,name))!==undefined){
18                     return ret;
19                 }else{
20                     return (elem[name] = value);
21                 }
22             //读取
23             }else{
24                 if(hooks && "get" && (ret = hooks.get(elem,value,name))!==undefined){
25                     return ret;
26                 }else{
27                     return elem[name];
28                 }
29             }
30         },

jQuery中调用access方法实现jQuery.fn.attr和jQuery.fn.prop:

1   attr: function( name, value ) {
2       return jQuery.access( this, name, value, true, jQuery.attr );
3   },
4 
5   prop: function( name, value ) {
6       return jQuery.access( this, name, value, true, jQuery.prop );
7   }

$.access方法源码:

多功能函数:读取或设置集合的属性值;值为函数时会被执行
  1>>elems:元素的集合,【collection】【类】数组
  2>>key:属性名称,key的只为object时,会拆解key为key,value形式再次执行jQuery.access
  3>>value:属性值
  4>>exec:在属性值为function时是否对设置之前的value值执行函数(这里为true)
  5>>fn:执行的函数
  6>>pass:是否设置为jQuery对象的属性  attr时使用
  

  用于fn:jQuery.fn.css(),jQuery.fn.attr(),jQueyr.fn.prop
  return jQuery.access( this, name, value, true, function( elem, name, value ) {});

说明:主要用于参数的循环处理,为什么要这样做?待详细分析

access:function(elems,key,value,exec,fn,pass){
                var elems = elems.length;
                //如果有多个属性就迭代
                if(typeof key === "object"){
                    for(var k in key){//key的参数循环处理
                        jQuery.access(elems,k,key[k],exec,fn,value);
                    }
                    return elems;
                }
                //只设置一个属性
                if(value !== undefined){
                    exec = !pass && exec && jQuery.isFunction(value);

                    for (var i=0;i<length;i++){
                        fn(elems[i],key, exec ? value.call(elems[i],i,fn(elems[i],key)) : value,pass);
                    }
                    return elems;
                }
                //读取属性
                return length ? fn(elems[0],key) : undefined;

            },
原文地址:https://www.cnblogs.com/colorstory/p/2612959.html