jQuery源碼分析系列:屬性操做

屬性操做javascript

1.6.1相對1.5.x最大的改進,莫過於對屬性.attr()的重寫了。在1.6.1中,將.attr()一分爲二: .attr()、.prop(),這是一個使人困惑的變動,也是一個破壞性的升級,會直接影響到無數的網站和項目升級到1.6。css

簡單的說,.attr()是經過setAttribute、getAttribute實現,.prop()則經過Element[ name ]實現:html

 

jQuery.attrjava

setAttribute, getAttributenode

jQuery.removeAttrjquery

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,
        width: 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;

            },
相關文章
相關標籤/搜索