文章出處

一、前言                                

  或許你和我一樣都曾經被下面的代碼所困擾

var el = document.getElementById('dummy');
el.hello = "test";
console.log(el.getAttribute('hello')); // IE67下輸出test,其他瀏覽器輸出null

  “搞毛啊?”,苦逼的Jser對著瀏覽器大呼一聲。然后就用下面蹩腳的方式草草處理掉了。

function getAttr(el, prop){
  return el[prop] || el.getAttribute(prop);
}

// 相應的賦值函數
function setAttr(el, prop, val){
  el[prop] = val;
}
// 相應的清理函數
function removeAttr(el, prop){
  delete el[prop];
}

 

 雖然算是搞定了,但那到底什么是屬性(Property)?什么是特性(Attribute)?還是傻傻分不清楚。有幸拜讀了司徒正美的書,終于明白兩者的區別,下面的內容為書中內容和項目中踩坑得來,實屬不易啊!

 

二、從語義理解Property和Attribute                  

  Property和Attribute均為外來詞,首先我們看看它們的翻譯究竟是什么先吧!

  Property:屬性、所有權,強調主題對象的特征

  Attribute:屬性、特性,強調主題對象的有別其他對象的特征

  從上述語義推斷,Attribute應該是Property的子集。

  但不幸的是,瀏覽器并不這樣理解,即使符合W3C標準規范也不是這樣。

 

三、W3C規定Property和Attribute含義                  

   看看圖更健康

   

  可以看到元素的“屬性”被分為三塊

  1. standard attribute:標準屬性(或固有屬性),如id、name等DTD/Scheme中定義的標簽屬性。

    特點:通過點方式或getAttribute均可訪問、設置。

  2. custom property:自定義屬性,通過點方式訪問、設置的非DTD/Scheme中定義的標簽屬性。

    特點:僅僅能通過點方式操作屬性。

  3. custom attribute:自定義特性(顯式特性),直接寫在標簽中或通過getAttribute等APIs訪問、設置的非DTD/Scheme中定義的標簽屬性

    特點:①. 可通過在html標簽中顯式聲明,如<div customAttr="attrValue"></div>

       ②. 通過getAttribute等APIs操作屬性。

  而從IE8開始各大瀏覽器在這方面就遵守W3C標準了,所以就出現前言下代碼片段的兼容性問題了。

 

四、custom attribute的類型——[object Attr]                  

 我想大家了解一下[object Attr]類型對理解后續內容會有幫助,于是就在這里打個岔了。

   custom attribute類型的屬性對象類型就是[object Attr]。

 瀏覽器支持:IE8+(IE567以[object Object]類型的形式提供與[object Attr]類型相同的APIs)、FF、Chrome

   特點

  ①. 雖然Attr被視為節點,但卻不作為DOM樹的一部分,因此沒有父節點,也不屬于所在html節點的子節點;

  ②. Attr節點的值為字符串(IE567除外),因此通過setAttribute等賦予非字符串類型的值時,會進行隱式類型轉換。

 屬性值

屬性名 值或功能說明
nodeType 2
nodeName 屬性名
nodeValue {Text} 屬性值
parentNode null
childNodes IE8返回null;IE9+和Chrome就返回以屬性值(屬性值類型為[object Text])為元素的NodeList對象;FF30.0就返回空的NodeList。
name   和nodeName一致
value 和nodeValue一致
textContent 設置或返回屬性的文本內容
specified 用于判斷屬性值是否為自定義值,true表示是在文檔中自定義設置的;false表示是DTD/Scheme設置的默認值。

 

 創建:document.createAttribute({String} 屬性名)

   直接操作

HTMLElement對象.setAttributeNode({Attr} attr);
HTMLElement對象.getAttributeNode({String} 屬性名);
HTMLElement對象.removeAttributeNode({Attr} attr); // 返回被刪除的Attr節點

 注意:HTMLElement對象.removeAttributeNode({Attr} attr),當HTMLElement對象沒有attr屬性時,調用該方法會拋異常(NotFoundError: Failed to execute 'removeAttributeNode' on 'Element': The node provided is owned by another element.)

  間接操作

HTMLElement對象.setAttribute({String} 屬性名, {Any} 屬性值);
HTMLElement對象.getAttribute({String} 屬性名);
HTMLElement對象.removeAttribute({String} 屬性名);
HTMLElement對象.hasAttribute({String} 屬性名); // IE8+才有方法,用于判斷元素是否擁有該特性

 注意:HTMLElement對象.removeAttribute({String} 屬性名),當HTMLElement對象沒有指定屬性名的屬性時,采用靜默模式處理(就是刪除成功一樣返回undefined)

 

五、點方式——custom property的專屬操作方式              

var el = document.getElementById('dummy');
el.id; // 點方式
el['hello'] = 'test'; // 點方式

 

六、判斷standard attribute的方式                    

  我們可以通過點方式和getAttribute等方式訪問standard attribute,但到底哪些是standard attribute哪些不是呢?下面介紹兩種方式去判斷。

  ①. 查閱http://msdn.microsoft.com/library/ms533029%28v=VS.85%29.aspx

  ②. 由司徒正美提供思路(生產環境中應該加入緩存從而提高性能)

// IE5+、Chrome、FF均有效
function isStandardAttr(node, prop){
// 由于window、document沒有getAttribute等方法,這里暫時返回false好了
if (!node.getAttribute) return false; var nakedNode = document.createElement(node.nodeName); return !(nakeNode[prop] === void 0 && nakeNode.getAttribute(prop) === null); }

  非standard attribute在未賦值時,點方式訪問會返回undefine,而getAttribute方式訪問會返回null。

  而standard attribute在未賦值時,點方式訪問會返回屬性的默認值(title、id等會返回空字符串,而checked會返回false),而getAttribute方式訪問會返回null。不利于判斷。因此采用上一段的方式判斷。

 

七、對于standard attribute,點方式和getAttribute方式操作的區別  

  首先要明確一點,通過點方式可對屬性賦值任意js數據類型的屬性值,通過setAttribute方式賦值則會自動對入參進行序列化后賦予給屬性。因此點方式操作的任意js數據類型,而getAttribute等方法操作字符串類型的屬性值。

  區別1,獲取的屬性值不同:

  點方式訪問時是對屬性值進行計算后的結果,getAttribute方式訪問的是靜態屬性值。

  以href屬性為例,所在文件:c:\test.html,html標簽:<a href="${鏈接1}"></a>:

瀏覽器 點方式 點方式結果 getAttribute getAttribute結果
 IE8+ 絕對路徑,符號被編碼,中文不被編碼 file:///c:/$%7B鏈接1%7D 原屬性值 ${鏈接1}
Chrome、FF 絕對路徑,符號被編碼,中文被編碼 file:///c:/$%7B%E9%93%BE%E6%8E%A51%7D" 原屬性值 ${鏈接1}

    

  區別2,屬性名不同:

  對于某些standard attribute而言,同一個屬性,點方式和getAttribute方式分別使用不同的屬性名來操作。

點方式 getAttribute
className class
htmlFor for
style.cssText style

 

八、困惑的焦點——standard property                       

   由于可通過點方式和getAttribute的方式操作standard property,因此相對其他兩種屬性而言,standard property是最復雜的。

下面我將其再細分為

  ①. 普通屬性(如id、name等)

    點方式和getAttribute方式操作一致,屬性值自動轉換為String類型。

 

  ②. 樣式屬性(style屬性)

    點方式的dom.style.cssText對應dom.getAttribute('style')操作,兩者均是獲取String類型屬性值。在賦予正常的樣式規則時,

各瀏覽器行為均一致。但復雜度就在當賦予異常的樣式規則時,各瀏覽器行為如下:

賦值方式 點方式訪問 getAttribute方式訪問 瀏覽器
點方式 空字符串 null Chrome、FF
setAttriubte 空字符串 通過setAttribute設置的無效樣式規則屬性值
點方式 空字符串 null IE9
setAttribute 空字符串 空字符串
點方式 空字符串 空字符串

IE8,10,11

setAttribute 空字符串 空字符串

    注意:IE8—11下,當通過setAttribute設置異常的樣式規則時,html標簽中的style屬性會被刪除,因此無法通過outerHTML來萃取異常樣式規則的字符串值。

 

  ③. 布爾屬性(如checked、disabled、selected等)

    在折騰時發現同樣是布爾屬性,但特征卻不盡相同,因此暫時給出如下分類。

    3.1. 一般布爾屬性(如disabled、IE5678下的checked)

賦值方式 賦予的值 點方式訪問 getAttribute方式訪問
點方式 true true 空字符串
false false null
setAttribute
空字符串 true 空字符串
非disabled的任意字符串 true

IE9+、Chrome和FF是返回setAttribute設置的值;

IE8是CHECKED

removeAttribute   false

null

       通過setAttribute方式設置,只需出現布爾屬性名稱,布爾屬性值即為true;

       兩種方式操作結果相互同步。

          2014/12/08添加:

        注意:FF下LINK元素的disabled屬性是Attribute和Property關系分離的,兩者互不影響。而樣式是否應用于頁面元素則由Property決定,并且當且僅當LINK元素被添加到渲染樹后才能通過點方式設置disabled的值,否則設置均無效并還原為默認值false。

 

      3.2. non-removeAttr的布爾屬性(如selected)

         確實不知道起什么名字好,于是只好暫時這樣稱呼吧。它的行為特征就是除了removeAttribute操作無法改變點方式獲取的屬性值內容外,

       其他行為與一般布爾屬性一樣。具體如下:

       所屬SELECT元素為單選模式:

        通過點方式操作selected屬性時,true表示選中,false表示不選中;通過setAttribute時,表示選中,且點方式訪問selected時會返回true;
      但通過removeAttribute移除selected屬性后,并不會改變選中項,因為selectedIndex沒有被改變。
        推斷:option標簽設置selected顯式屬性后,會改變selectedIndex的值,從而改變選中項;而removeAttribute時僅僅去除該屬性,
         而沒有改變selectedIndex值,因此不會改變選中項。而點方式是根據selectedIndex去獲取項目是否被選中。
 
      所屬SELECT元素為多選模式:
          通過點方式操作selected屬性時,true表示選中,false表示不選中;通過setAttribute時,表示選中,且點方式訪問selected時會返回true;
      但通過removeAttribute移除selected屬性后,并不會改變選中項。

 

    3.3. 變異布爾屬性(如IE9+、Chrome和FF下checked)

      變異布爾屬性最大的特點是,在用戶UI改動屬性值和通過點方式改動屬性值前,點方式和getAttribute方式是操作同一個屬性。但經過用戶UI或點方式改動屬性值后,兩者操作的就是同名的兩個屬性了,此時點方式操作才是與UI狀態關聯的屬性。

      具體代碼如下:

IE9+、Chrome和FF下CHECKBOX和RADIO元素的checked屬性屬于變異布爾屬性,而IE5678下的checked屬性就屬于雙向布爾屬性。

var cbx = document.createElement('input');
cbx.type = 'checkbox';

// UI或點方式改動屬性前
cbx.setAttribute('checked', '');
console.log(cbx.checked); // 返回true
cbx.removeAttribute('checked');
console.log(cbx.checked); // 返回false


// 點方式改動屬性后
cbx.checked = true;
console.log(cbx.checked); // 返回true
console.log(cbx.getAttribute('checked')); // 返回null

cbx.checked = false;
console.log(cbx.checked); // 返回false
cbx.setAttribute('checked', '');
console.log(cbx.checked); // 返回false

 

  ④. 事件鉤子(如onclick等)

    事件鉤子是DOM0級的事件訂閱方式,現在一般不怎么用了,但不妨礙我們去折騰。

    而折騰的結果是卻是讓人驚奇的,因為它與之前理解的standard attribute的特征有差異,那就是點方式和getAttribute方式操作是單向影響的。

    具體請看代碼(IE8-11,Chrome,FF均如此):

var dom = document.createElement('DIV');
dom.setAttribute('onclick', 'console.log("bySA");');

/* 輸出
 * function onclick(){
 *   console.log("bySA");
 * }
 */
console.log(dom.onclick);
/* 輸出
 *   console.log("bySA");
 */
console.log(dom.getAttribute('onclick'));


dom.onclick = function(){
   console.log('byProp'); 
};
/* 輸出
 * dom.onclick = function(){
 *   console.log('byProp'); 
 * };
 */
console.log(dom.onclick);
/* 輸出
 *   console.log("bySA");
 */
console.log(dom.getAttribute('onclick'));

// 輸出byPorp
dom.click();

  整體來說,就是setAttribute方式設置的事件鉤子點方式可見,點方式設置的事件鉤子getAttribute不可見。

 

  ⑤. 值屬性(value屬性)

    用過JQuery都知道面對種類繁多的表單元素,一個val函數就能輕松搞定是一件多么愜意的事啊。但原生value屬性到底有哪些坑呢?我們現在來踩一下。

    5.1. SELECT標簽

        下拉框有單選(select-one)和多選(select-multiple)兩種模式。而它的value屬性由于是特性value和被選中項的text屬性的運算結果,

      因此建議使用點方式進行操作。

      通過點方式獲取和設置value值的運算流程如下:

瀏覽器 操作 流程

selectedIndex

默認值

Chrome、FF 獲取 獲取的第一被選中的option的value屬性,若沒有設置value屬性,則返回該option標簽的text屬性

單選:0

多選:-1

設置 會根據屬性值去匹配option標簽的value屬性值,若匹配成功則該option將被選中;若不成功,則匹配option的text屬性值。若成功,則selectedIndex設置為-1。再次通過點方式訪問value時,返回空字符串。
IE9+ 獲取 獲取的第一被選中的option的value屬性,若沒有設置value屬性,則返回該option標簽的text屬性

單選:0

多選:-1

設置 會根據屬性值去匹配option標簽的value屬性值,若匹配成功則該option將被選中;若不成功,則selectedIndex設置為-1。再次通過點方式訪問value時,返回空字符串。
IE5678 獲取 獲取的第一被選中的option的value屬性,若沒有設置value屬性則返回空字符串。 單選、多選:-1
設置 會根據屬性值去匹配option標簽的value屬性值,若匹配成功則該option將被選中;若不成功,則selectedIndex設置為-1。再次通過點方式訪問value時,返回空字符串。

      text屬性:屬性值就是選中項的innerText.trim()返回的字符串。

      結論:通過SELECT元素的value屬性獲取選中項的值不可靠,因此mass framework在valHooks['@select:get']中是通過操作OPTION元素來獲取選中項的值,

           并且由于SELECT元素或OPTION元素的disabled屬性值為true時,OPTION元素的selected屬性依舊可能返回true,因此要對不可用的OPTION元素作過濾。

 

    5.2. CHECKBOX和RADIO標簽

       value屬性默認為字符串"on"

      

  ⑥. Url屬性(如href、src等)

    點方式獲取的屬性值為絕對路徑且對特殊符號、中文進行編碼,getAttribute方式獲取的原來的值。

    

  看到這里我想大家都有點頭暈暈了吧,總結一下感覺會好些!

  1. 對于普通屬性,兩種方式均可;

  2. 對于樣式、布爾和事件鉤子屬性建議統一采用點方式操作;

  3. 對于值屬性要不就使用JQuery等dom庫統一操作,要不就具體元素具體操作吧,

    mass framework的valHooks['@select:get']就是遍歷option元素來獲取select的選中值的;

  4. Url屬性看具體需求,若想獲取絕對路徑,那就用點方式吧,否則就用getAttribute吧!

 

九、window和document對象的屬性分類            

  由于window和document對象均沒有getAttribute函數,可知其必須沒有custom attribute的。但我們還是可以將它們的屬性分為固有屬性和自定義屬性。

  固有屬性:window和document對象自身攜帶的成員屬性和方法;

          特征:①. 無法通過delete操作刪除固有屬性,在IE5.5、6、7中還會拋異常呢!

           ②. 固有屬性為只讀屬性,無法修改。

  自定義屬性:Jser們附加到window和document對象上的屬性和方法。

       特征:①. 可通過delete操作刪除;

            ②. 自定義屬性可隨便改。

  下面我將固有屬性的判斷和本文第六節中判斷standard attribute的方法結合一下:

// IE5+、Chrome、FF均有效
function isStandardAttr(node, prop){
  // 由于window、document沒有getAttribute等方法,這里用固有屬性為只讀的特征來作判斷
  if (!node.getAttribute){
    var oVal = node[prop], nVal;
    nVal = node[prop] = +new Date();
    node[prop] = oVal;
    
    return nVal === oVal;
  }  

  var nakedNode = document.createElement(node.nodeName);
  return !(nakeNode[prop] === void 0 && nakeNode.getAttribute(prop) === null);
}

 

十、IE5.5、6、7下的特性與屬性                

  custom property和custom attribute都是IE8+的分類,在IE5.5、6、7下它倆可是一伙的。于是會發現在IE7下,dom.getAttribute('style')得到居然是個對象而不是樣式規則的字符串。也許你會覺得這不礙事,反正在獲取style屬性時直接用點方式就好了。但下面的情況一不注意就會中bug了。

  情況①:調用FORM元素的getAttribute獲取action屬性,居然得到其下的表單元素?

      html

<form action="./add.aspx" name="frm" id="frm">
    <input type="text" id="name"/>
    <input type="text" name="id"/>
    <select id="action">
       <option value="0">所有</option>
    </select>
</form>    

      js

var fDom = document.getElementById('frm');

var action = fDom.getAttribute('action');
var name = fDom.geAttribute('name');
var id = fDom.geAttribute('id');

console.log(typeof action);// 返回object
console.log(action.id);// 返回action

console.log(typeof name);// 返回object
console.log(name.id);// 返回name

console.log(typeof id);// 返回object
console.log(id.name);// 返回id

  也許大家會疑惑,這最多就是通過點方式獲取FORM元素的屬性值而已,為什么會獲取其下id或name屬性值匹配的表單元素呢?假如大家看過《JS魔法堂:那些困擾你的DOM集合》就會知道FORM元素有一個HTMLFormControllersCollection類型的elements屬性,該屬性可通過點方式獲取FORM元素下id或name屬性值匹配的表單元素。而該FORM元素直接擁有elements這一特征,因此點方式除了獲取FORM元素自身的屬性值外,還可以訪問其下的表單元素。

  解決辦法采用getAttributeNode獲取Attr類型對象

var actionNode = fDom.getAttributeNode('action');
var nameNode = fDom.geAttributeNode('name');
var idNode = fDom.geAttributeNode('id');

console.log(actionNode.value); // ./add.aspx
console.log(nameNode.value); // frm
console.log(idNode.value); // frm

 

  情況②:沒法通過href、src等獲取絕對路徑?

       與操作其他屬性不同,對于href、src等屬性而言,點方式的行為特征被getAttribute同化了,僅能獲取靜態屬性值。那怎么辦呢?

    IE對getAttribute作了增強,具體如下。

      getAttrbute({String} 屬性名, {Number} [0|1|2|4]):默認值0,表示使用IE默認行為;

                                  1,屬性名區分大小寫;

                                  2,獲取屬性的靜態屬性值;

                                  4,獲取絕對路徑。

 

十一、其他:IE下tabIndex屬性的非標準行為          

    標準瀏覽器(Chrome、FF等)下僅僅是表單元素(a,area)和鏈接元素(input,button,select,textarea,object)的tabIndex屬性的默認值為0,

  而其他元素的tabIndex默認值為-1。而IE下就是所有元素的tabIndex屬性默認值均為0.

 

十二、總結                          

      本來是打算針對IE5.5、6、7和其他瀏覽器的差異、IE的bugs和各類型屬性的特點來修補getAttribute等方法,但發現屬性系統水深啊,

為原生API打補丁成本高效益差,還不如像JQuery那樣重新包裝上市來得爽快。于是本篇僅僅是記錄了屬性系統的一些坑而已,還不夠全面,也暫時沒能提出有效的封裝方式。

  想繼續深入的朋友可以讀讀個大框架的源碼哦!

 

 十三、感謝那些巨人                      

  《JavaScript框架設計》

  《JQuery源碼分析》 http://www.cnblogs.com/aaronjs/p/3279314.html

  尊重原創,轉載請注明來自:http://www.cnblogs.com/fsjohnhuang/p/3840263.html ^_^肥仔John

 


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()