property&attribute

2017年4月6日
DOM 中 Property 和 Attribute 的區別轉載:博客園
property 和 attribute非常容易混淆,兩個單詞的中文翻譯也都非常相近(property:屬性,attribute:特性),但實際上,二者是不同的東西,屬于不同的范疇。
property是DOM中的屬性,是JavaScript里的對象;
attribute是HTML標簽上的特性,它的值只能夠是字符串;
基于JavaScript分析property 和 attribute
html中有這樣一段代碼:
簡單的在html頁面上創建一個input輸入欄(注意在這個標簽中添加了一個DOM中不存在的屬性“sth”),此時在JS執行如下語句
var in1 = document.getElementById('in_1');
執行語句
console.log(in1);
從console的打印結果,可以看到in1含有一個名為“attributes”的屬性,它的類型是NamedNodeMap,同時還有“id”和“value”兩個基本的屬性,但沒有“sth”這個自定義的屬性。
attributes: NamedNodeMap
value: "1"
id: "in_1"
有些console可能不會打印in1上的屬性,那么可以執行以下命令打印要觀察的屬性:
console.log(in1.id); // 'in_1'
console.log(in1.value); // 1
console.log(in1.sth); // undefined
可以發現,標簽中的三個屬性,只有“id”和“value”會在in1上創建,而“sth”不會被創建。這是由于,每一個DOM對象都會有它默認的基本屬性,而在創建的時候,它只會創建這些基本屬性,我們在TAG標簽中自定義的屬性是不會直接放到DOM中的。
我們做一個額外的測試,創建另一個input標簽,并執行類似的操作:
html:
JS:
var in2 = document.getElementById('in_2');
console.log(in2);
從打印信息中可以看到:
id: "in_2"
value: null
盡管我們沒有在TAG中定義“value”,但由于它是DOM默認的基本屬性,在DOM初始化的時候它照樣會被創建。由此我們可以得出結論:
DOM有其默認的基本屬性,而這些屬性就是所謂的“property”,無論如何,它們都會在初始化的時候再DOM對象上創建。
如果在TAG對這些屬性進行賦值,那么這些值就會作為初始值賦給DOM的同名property。
現在回到第一個input(“#in_1”),我們就會問,“sth”去哪里了?別急,我們把attributes這個屬性打印出來看看
console.log(in2);
上面有幾個屬性:
0: id
1: value
2: sth
length: 3
proto: NamedNodeMap
原來“sth”被放到了attributes這個對象里面,這個對象按順序記錄了我們在TAG中定義的屬性和屬性的數量。此時,如果再將第二個input標簽的attributes打印出來,就會發現只有一個“id”屬性,“length”為1。
從這里就可以看出,attributes是屬于property的一個子集,它保存了HTML標簽上定義屬性。如果再進一步探索attitudes中的每一個屬性,會發現它們并不是簡單的對象,它是一個Attr類型的對象,擁有NodeType、NodeName等屬性。關于這一點,稍后再研究。注意,打印attribute屬性不會直接得到對象的值,而是獲取一個包含屬性名和值的字符串,如:
console.log(in1.attibutes.sth); // 'sth="whatever"'
由此可以得出:
HTML標簽中定義的屬性和值會保存該DOM對象的attributes屬性里面;
這些attribute屬性的JavaScript中的類型是Attr,而不僅僅是保存屬性名和值這么簡單;
那么,如果我們更改property和attribute的值會出現什么效果呢?執行如下語句:
in1.value = 'new value of prop';
console.log(in1.value); // 'new value of prop'
console.log(in1.attributes.value); // 'value="1"'
此時,頁面中的輸入欄的值變成了“new value of prop”,而propety中的value也變成了新的值,但attributes卻仍然是“1”。從這里可以推斷,property和attribute的同名屬性的值并不是雙向綁定的。
如果反過來,設置attitudes中的值,效果會怎樣呢?
in1.attributes.value.value = 'new value of attr';
console.log(in1.value); // 'new value of attr'
console.log(in1.attributes.value); // 'new value of attr'
此時,頁面中的輸入欄得到更新,property中的value也發生了變化。此外,執行下面語句也會得到一樣的結果
in1.attributes.value.nodeValue = 'new value of attr';
由此,可得出結論:
property能夠從attribute中得到同步;
attribute不會同步property上的值;
attribute和property之間的數據綁定是單向的,attribute->property;
更改property和attribute上的任意值,都會將更新反映到HTML頁面中;
基于jQuery分析attribute和property
那么jQuery中的attr和prop方法是怎樣的呢?
首先利用jQuery.prop來測試
$(in1).prop('value', 'new prop form $');
console.log(in1.value); // 'new prop form $'
console.log(in1.attributes.value); // '1'
輸入欄的值更新了,但attribute并未更新。
然后用jQuery.attr來測試
$(in1).attr('value', 'new attr form $');
console.log(in1.value); // 'new attr form $'
console.log(in1.attributes.value); // 'new attr form $'
輸入欄的值更新了,同時property和attribute都更新了。
從上述測試的現象可以推斷,jQuery.attr和jQuery.prop基本和原生的操作方法效果一致,property會從attribute中獲取同步,然而attribute不會從property中獲取同步。那么jQuery到底是如何實現的呢?
下面,我們來看看jQuery.attr和jQuery.prop的源碼。
jQuery源碼
$().prop源碼

   prop: function( name, value ) {
       return access( this, jQuery.prop, name, value, arguments.length > 1 );
   },
   ... // removeProp方法
});```
$().attr源碼
```jQuery.fn.extend({
   attr: function( name, value ) {
       return access( this, jQuery.attr, name, value, arguments.length > 1 );
   },
   ... // removeAttr方法
});```
無論是attr還是prop,都會調用access方法來對DOM對象的元素進行訪問,因此要研究出更多內容,就必須去閱讀access的實現源碼。
```jQuery.access
// 這是一個多功能的函數,能夠用來獲取或設置一個集合的值
// 如果這個“值”是一個函數,那么這個函數會被執行
// @param elems, 元素集合
// @param fn, 對元素進行處理的方法
// @param key, 元素名
// @param value, 新的值
// @param chainable, 是否進行鏈式調用
// @param emptyGet,
// @param raw, 元素是否一個非function對象
var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
   var i = 0,                      // 迭代計數
       length = elems.length,      // 元素長度
       bulk = key == null;         // 判斷是否有特定的鍵(屬性名)
   // 如果存在多個屬性,遞歸調用來逐個訪問這些值
   if ( jQuery.type( key ) === "object" ) {
       chainable = true;
       for ( i in key ) {
           jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
       }
   // 設置一個值
   } else if ( value !== undefined ) {
       chainable = true;
       if ( !jQuery.isFunction( value ) ) {    // 如果值不是一個function
           raw = true;
       }
       if ( bulk ) {
           // Bulk operations run against the entire set
           // 如果屬性名為空且屬性名不是一個function,則利用外部處理方法fn和value來執行操作
           if ( raw ) {
               fn.call( elems, value );
               fn = null;
           // ...except when executing function values
           // 如果value是一個function,那么就重新構造處理方法fn
           // 這個新的fn會將value function作為回調函數傳遞給到老的處理方法
           } else {
               bulk = fn;
               fn = function( elem, key, value ) {
                   return bulk.call( jQuery( elem ), value );
               };
           }
       }
       if ( fn ) { // 利用處理方法fn對元素集合中每個元素進行處理
           for ( ; i < length; i++ ) {
               fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
               // 如果value是一個funciton,那么首先利用這個函數返回一個值并傳入fn
           }
       }
   }
   return chainable ?
       elems :         // 如果是鏈式調用,就返回元素集合
       // Gets
       bulk ?
           fn.call( elems ) :
           length ? fn( elems[0], key ) : emptyGet;
};```
access方法雖然不長,但是非常繞,要完全讀懂并不簡單,因此可以針對jQuery.fn.attr的調用來簡化access。
jQuery.fn.attr/ jQuery.fn.prop 中的access調用
$().attr的調用方式:
$().attr( propertyName ) // 獲取單個屬性
$().attr( propertyName, value ) // 設置單個屬性
$().attr( properties ) // 設置多個屬性
$().attr( propertyName, function ) // 對屬性調用回調函數
prop的調用方式與attr是一樣的,在此就不重復列舉。為了簡單起見,在這里只對第一和第二種調用方式進行研究。
調用語句:
access( this, jQuery.attr, name, value, arguments.length > 1 );
簡化的access:
```// elems 當前的jQuery對象,可能包含多個DOM對象
// fn jQuery.attr方法
// name 屬性名
// value 屬性的值
// chainable 如果value為空,則chainable為false,否則chainable為true
var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
   var i = 0,                      // 迭代計數
       length = elems.length,      // 屬性數量
       bulk = false;               // key != null
   if ( value !== undefined ) {    // 如果value不為空,則為設置新值,否則返回該屬性的值
       chainable = true;
       raw = true;             // value不是function
       if ( fn ) { // fn為jQuery.attr
           for ( ; i < length; i++ ) {
               fn( elems[i], key, value);      // jQuery.attr(elems, key, value);
           }
       }
   }
if(chainable) {         // value不為空,表示是get
return elems;       // 返回元素實現鏈式調用
} else {
if(length) {        // 如果元素集合長度不為零,則返回第一個元素的屬性值
return fn(elems[0], key);   // jQuery.attr(elems[0], key);
} else {
return emptyGet;        // 返回一個默認值,在這里是undefined
}
}
};```
通過簡化代碼,可以知道,access的作用就是遍歷上一個$調用得到的元素集合,對其調用fn函數。在jQuery.attr和jQuery.prop里面,就是利用access來遍歷元素集合并對其實現對attribute和property的控制。access的源碼里面有多段條件轉移代碼,看起來眼花繚亂,其最終目的就是能夠實現對元素集合的變量并完成不同的操作,復雜的代碼讓jQuery的接口變得更加簡單,能極大提高代碼重用性,意味著減少了代碼量,提高代碼的密度從而使JS文件大小得到減少。
這些都是題外話了,現在回到$().attr和$().prop的實現。總的說,這兩個原型方法都利用access對元素集進行變量,并對每個元素調用jQuery.prop和jQuery.attr方法。要注意,這里的jQuery.prop和jQuery.attr并不是原型鏈上的方法,而是jQuery這個對象本身的方法,它是使用jQuery.extend進行方法擴展的(jQuery.fn.prop和jQuery.fn.attr是使用jQuery.fn.extend進行方法擴展的)。
下面看看這兩個方法的源碼。
jQury.attr
```jQuery.extend({
attr: function( elem, name, value ) {
var hooks, ret,
nType = elem.nodeType;  // 獲取Node類型
       // 如果 elem是空或者NodeType是以下類型
       //      2: Attr, 屬性, 子節點有Text, EntityReference
       //      3: Text, 元素或屬性中的文本內容
       //      8: Comment, 注釋
       // 不執行任何操作
       if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
           return;
       }
       // 如果支持attitude方法, 則調用property方法
       if ( typeof elem.getAttribute === strundefined ) {
           return jQuery.prop( elem, name, value );
       }
       // 如果elem的Node類型不是元素(1)
       if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
           name = name.toLowerCase();
           // 針對瀏覽器的兼容性,獲取鉤子函數,處理一些特殊的元素
           hooks = jQuery.attrHooks[ name ] ||
               ( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
       }
       if ( value !== undefined ) {        // 如果value不為undefined,執行"SET"
           if ( value === null ) {         // 如果value為null,則移除attribute
               jQuery.removeAttr( elem, name );
           } else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
               return ret;                 // 使用鉤子函數
           } else {                        // 使用Dom的setAttribute方法
               elem.setAttribute( name, value + "" );      // 注意,要將value轉換為string,因為所有attribute的值都是string
               return value;
           }
       // 如果value為undefined,就執行"GET"
       } else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
           return ret;         // 使用鉤子函數
       } else {
           ret = jQuery.find.attr( elem, name );   // 實際上調用了Sizzle.attr,這個方法中針對兼容性問題作出處理來獲取attribute的值
           // 返回獲得的值
           return ret == null ?
               undefined :
               ret;
       }
   },
   ...
});```
從代碼可以發現,jQuery.attr調用的是getAttribute和setAttribute方法。
jQeury.prop
```jQuery.extend({
   ...
prop: function( elem, name, value ) {
var ret, hooks, notxml,
nType = elem.nodeType;
       // 過濾注釋、Attr、元素文本內容
       if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
           return;
       }
       notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
       if ( notxml ) {     // 如果不是元素
           name = jQuery.propFix[ name ] || name;  // 修正屬性名
           hooks = jQuery.propHooks[ name ];       // 獲取鉤子函數
       }
       if ( value !== undefined ) {        // 執行"SET"
           return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?
               ret :                       // 調用鉤子函數
               ( elem[ name ] = value );   // 直接對elem[name]賦值
       } else {                            // 執行"GET"
           return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
               ret :               // 調用鉤子函數
               elem[ name ];       // 直接返回elem[name]
       }
   },
   ...
});```
jQuery.prop則是直接對DOM對象上的property進行操作。
通過對比jQuery.prop和jQuery.attr可以發現,前者直接對DOM對象的property進行操作,而后者會調用setAttribute和getAttribute方法。setAttribute和getAttribute方法又是什么方法呢?有什么效果?
setAttribute和getAttribute
基于之前測試使用的輸入框,執行如下代碼:
```in1.setAttribute('value', 'new attr from setAttribute');
console.log(in1.getAttribute('value'));         // 'new attr from setAttribute'
console.log(in1.value);                         // 'new attr from setAttribute'
console.log(in1.attributes.value);              // 'value=?"new attr from                             setAttribute'實際是一個Attr對象```
執行完setAttribute以后,就如同直接更改attributes中的同名屬性;
而getAttribute的結果與訪問property的結果一模一樣,而不會像直接訪問attritudes那樣返回一個Attr對象。
特殊的例子
href
然而,是不是所有標簽,所有屬性都維持保持這樣的特性呢?下面我們看看href這個屬性/特性。
首先在html中創建一個標簽:
```<a href='page_1.html'id='a_1'></a>```
在JS腳本中執行如下代碼:
```console.log(a1.href);   // 'file:///D:/GitHub/JS/html/test_01/page_1.html'
console.log(a1.getAttribute('href'));   // 'page_1.html'```
可以看到,property中保存的是絕對路徑,而attribute中保存的是相對路徑。那么,如果更改了這些值會發生什么情況呢?
更改attribute:
```a1.setAttribute('href', 'page_2.html');     // 相對路徑
console.log(a1.href);   // 'file:///D:/GitHub/JS/html/test_01/page_2.html'
console.log(a1.getAttribute('href'));   // 'page_2.html'
a1.setAttribute('href', '/page_3.html');    // 根目錄路徑
console.log(a1.href);                       // 'file:///D:/page_3.html'
console.log(a1.getAttribute('href'));       // '/page_3.html'```
更改property:
```a1.href = 'home.html';  // 相對路徑
console.log(a1.href);   // 'file:///D:/GitHub/JS/html/test_01/home.html'
console.log(a1.getAttribute('href'));   // 'home.html'
a1.href = '/home.html'; // 根目錄路徑
console.log(a1.href);   // 'file:///D:/home.html'
console.log(a1.getAttribute('href'));   // '/home.html'```
從這里可以發現,href是特殊的屬性/特性,二者是雙向綁定的,更改任意一方,都會導致另一方的的值發生改變。而且,這并不是簡單的雙向綁定,property中的href永遠保存絕對路徑,而attribute中的href則是保存相對路徑。
看到這里,attribute和property的區別又多了一點,然而,這又讓人變得更加疑惑了。是否還有其他類似的特殊例子呢?
id
嘗試改變property中的id:
```   a1.id = 'new_id';
   console.log(a1.id);                     // 'new_id'
   console.log(a1.getAttribute('id'));     // 'new_id'```
天呀,現在attribute中的id從property中的id發生了同步,數據方向變成了property <=> attribute;
disabled
再來看看disabled這個屬性,我們往第一個添加“disabled”特性:
 // 此時input已經被禁用了
然后執行下面的代碼:
```console.log(in1.disabled);      // true
in1.setAttribute('disabled', false);    // 設置attribute中的disabled,無論是false還是null都不會取消禁用
console.log(in1);               // true
console.log(in1.getAttribute('disabled'));  // 'false'```
改變attributes中的disabled不會改變更改property,也不會取消輸入欄的禁用效果。
如果改成下面的代碼:
```console.log(in1.disabled);      // true
in1.disabled = false;           // 取消禁用
console.log(in1.disabled);      // false
console.log(in1.getAttribute('disabled'));  // null,attribute中的disabled已經被移除了```
又或者:
```console.log(in1.disabled);      // true
in1.removeAttribute('disabled');    // 移除attribute上的disabled來取消禁用
console.log(in1.disabled);      // false
console.log(in1.getAttribute('disabled'));  // null,attribute中的disabled已經被移除了```
可以發現,將property中的disabled設置為false,會移除attributes中的disabled。這樣數據綁定又變成了,property<=>attribute;
所以property和attritude之間的數據綁定問題并不能單純地以“property<-attribute”來說明。
總結
分析了這么多,對property和attribute的區別理解也更深了,在這里總結一下:
創建
DOM對象初始化時會在創建默認的基本property;
只有在HTML標簽中定義的attribute才會被保存在property的attributes屬性中;
attribute會初始化property中的同名屬性,但自定義的attribute不會出現在property中;
attribute的值都是字符串;
數據綁定
attributes的數據會同步到property上,然而property的更改不會改變attribute;
對于value,class這樣的屬性/特性,數據綁定的方向是單向的,attribute->property;
對于id而言,數據綁定是雙向的,attribute<=>property;
對于disabled而言,property上的disabled為false時,attribute上的disabled必定會并存在,此時數據綁定可以認為是雙向的;
使用
可以使用DOM的setAttribute方法來同時更改attribute;
直接訪問attributes上的值會得到一個Attr對象,而通過getAttribute方法訪問則會直接得到attribute的值;
大多數情況(除非有瀏覽器兼容性問題),jQuery.attr是通過setAttribute實現,而jQuery.prop則會直接訪問DOM對象的property;
到這里為止,得出,property是DOM對象自身就擁有的屬性,而attribute是我們通過設置HTML標簽而給之賦予的特性,attribute和property的同名屬性/特性之間會產生一些特殊的數據聯系,而這些聯系會針對不同的屬性/特性有不同的區別。
事實上,在這里,property和attribute之間的區別和聯系難以用簡單的技術特性來描述,我在StackFlow上找到如下的回答,或者會更加接近于真正的答案:
These words existed way before Computer Science came around.
Attribute is a quality or object that we attribute to someone or something. For example, the scepter is an attribute of power and statehood.
Property is a quality that exists without any attribution. For example, clay has adhesive qualities; or, one of the properties of metals is electrical conductivity. Properties demonstrate themselves though physical phenomena without the need attribute them to someone or something. By the same token, saying that someone has masculine attributes is self-evident. In effect, you could say that a property is owned by someone or something.
To be fair though, in Computer Science these two words, at least for the most part, can be used interchangeably - but then again programmers usually don't hold degrees in English Literature and do not write or care much about grammar books :).
最關鍵的兩句話:
attribute(特性),是我們賦予某個事物的特質或對象。
property(屬性),是早已存在的不需要外界賦予的特質。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

推薦閱讀更多精彩內容