第十章:屬性模塊

一般咱們把對象的非函數成員叫屬性javascript

對於元素節點來講,其屬性大致分爲兩類固有屬性自定義屬性(特性)。固有屬性通常遵循駝峯命名風格,擁有默認值,而且沒法刪除。css

自定義屬性是用戶隨意添加的屬性值對,因爲元素節點也是一個普通的javascript對象,沒有什麼嚴格的訪問操做,所以命名風格林林總總,值的類型也是亂七八糟。可是隨意添加屬性顯然不夠安全,好比引發循環引用等,所以,瀏覽器提供了一組API來供人們操做自定義屬性html

setAtttribute, getAttribute, removeAttribute。固然還有其餘API。不過這是標準套裝。只有在IE67等糟糕的環境下,咱們纔會求助其餘的API、通常狀況下此三個屬性足也前端

咱們一般稱其爲DOM屬性系統DOM屬性系統對屬性名會進行小寫化處理,屬性值會統一轉字符串html5

    var el = document.createElement("div");
    el.setAttribute("xxx","1");
    el.setAttribute("XxX","2");
    el.setAttribute("XXx","3");

    console.log(el.getAttribute("xxx"))
    console.log(el.getAttribute("XxX"))

IE6,IE7會返回1,其餘瀏覽器返回3,在前端的世界裏,咱們走到哪都能碰到兼容性的問題。
IE67 在處理固有屬性時要求進行名字映射,好比class變成className,for變成htmlFor。java

對於布爾屬性(一些返回布爾的屬性),瀏覽器的差別更大。node

<input type="radio" id="aaa">
<input type="radio" checked  id="bbb">
<input type="radio" checked="checked"  id="ccc">
<input type="radio" checked="true" id="ddd">
<input type="radio" checked="xxx" id="eee">
<script type="text/javascript">
"aaa,bbb,ccc,ddd,eee".replace(/\w+/g,function (id) {
    var elem = document.getElementById(id)
    console.log(elem.getAttribute("checked"))
})
</script>

IE9 FF15 chrome23以上分別爲  null "" checked true xxx ,在ie 7 ie8差異太多,請自行嘗試。jquery

所以框架頗有必要提供一些API來屏蔽這些差別性但IE6時代,這個需求並不明顯。所以,ie67不區分固有屬性與自定義屬性。 setAttribute和getAttribute在當時人們看來就是一個語法糖。用el.setAttribute("innerHTML","xxxx")與用el.innerHTML = "xxxx"效果同樣,並且後者使用更方便。css3

即便早期使用普遍的prototype.js也提供的api也很是貧乏。chrome

只有identify,readAttribute,writeAttribute,hasAttribute,className,hasClassName,addClassName,removeClassName toggleClassName與$F方法。

後來prototype.js也意識到固有屬性和自定義屬性之間的差別。在它的內部,搞了個Element._attributeTranslations

然而,在其內部仍是優先el[name]方式來操做屬性,而不是set/getAttribute。

jQuery早期的attr方法,其行爲與prototype如出一轍。只 不過jQuery1.6以前,是使用attr方法實現了讀寫刪這三種操做。從易用性來講。不區分固有屬性和自定義屬性,由框架內部處理應該比attr,prop分家更容易接受。那麼是什麼導致jQuery這樣作呢答案是選擇器引擎

 

jQuery是最先以選擇器爲導向的類庫。它最開始的選擇器引擎是Xpath式,後來換成sizzle.以css表達式風格來選取元素,。在css.2.1引入了屬性選擇器[aaa=bbb],ie7也開始殘缺支持。Sizzle絕不含糊地實現了這語法。屬性選擇器是最先突破類名與ID的限制求元素的。

css3時代,屬性還能以[name^=value],[name*=value],[name$=value]等更精緻的方式來甄選元素,而這一切都是創建在獲取用戶的預設值的基礎上。所以jQuery下了很大的決心,把prop從attr切割出來。雖然爲了知足用戶的向前 兼容需求。又讓attr作了prop的事。


瀏覽器通過了這麼多年發展,誰也說不清元素節點有多少個屬性。for.in循環也不行,由於它對不可遍歷的屬性無能爲力。顯式屬性就是被顯式設置的屬性,分兩種狀況,一種是寫在標籤內的HTML屬性,一種是經過setAttribute動態設置的屬性。仍是以el[xxx]=yyy的定義屬性,仍是沒有定義的屬性。惋惜到ie8與其餘瀏覽器中,你只看到寥寥可數特性節點。稱爲顯式屬性

在IE或其它瀏覽器,咱們想斷定一個屬性是否爲顯式屬性,直接用hasAttribute API斷定

    var isSpecified = !"1"[0] ? function(el, attr) {
        return el.hasAttribute(attr)
    } : function(el, attr) {
        var val = el.attributes[attr]
        return !!val && val.specified
    }

二.className的操做

咱們操做一個屬性一般只有三個選擇:設置,讀取,刪除。但className有點特殊。它的值能夠用空格隔開,分爲多個類名。所以對類名的操做變成讀取,添加,刪除。在上代王者prototype.js,就已經把人們想要的類名操做總結出來。

咱們操做一個屬性一般只有三個選擇:設置,讀取,刪除。但className有點特殊。它的值能夠用空格隔開,分爲多個類名。所以對類名的操做變成讀取添加刪除。在上代王者prototype.js,就已經把人們想要的類名操做總結出來。

    //prototype1.7
    ClassName: function(element) {
        return new Element.ClassNames(element);
    },

    hasClassName: function(element, className) {
        if ((element = $(element))) return;
        var elementClassName = element.className;
        return (elementClassName.length >0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(s|$)").test(elementClassName)));
    },

    addClassName:function(element,className) {
        if (!(element = $(element))) return;
        if (!Element.hasClassName(element, className))
            element.className += (element.className ? ' ' : '') + className;
        return element;
    },

    removeClassName: function(element,className) {
        if (!(element = $(element))) return;
        element.className = element.className.replace(
            new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
        return element;
    },

    toggleClassName : function(element, className) {
        if (!(element = $(element))) return;
        return Element[Element.hasClassName(element, className) ? 'removeClassName' : 'addClassName'] (element,className)
    },

另外,除了這些外,還提供了一個Element.ClassNames類,用於直接操做類名,這不由把html5提供的classList聯想起來

prototype.js對className的操做已被承認,咱們亦能夠吧prototype.js精簡一下,變成一些工具函數。在不引入類庫時使用。

    var getClass = function(ele) {
        return ele.className.replace(/\s+/," ").split(" ");
    }

    var hasClassName = function(ele,cls){
        return -1 < (" "+ele.className+ " ").indexOf(" "+cls+" ");
    }

    var addClass = function(ele,cls) {
        if (!this.hasClass(ele,cls)) {
            ele.className += " "+ cls;
        }
    }

    var removeClass = function(ele,cls) {
        if (hasClass(ele,cls)){
            var reg = new RegExp ('(\\s|^)'+cls+'(\\s|$)');
            ele.className = ele.className.replace(reg," ");
        }
    }

    var clearClass = function(ele, cls) {
        ele.className = ""
    }

 

三.jQuery的屬性系統

但願分析早期的jQuery,帶來一點啓迪,jQuery系統經年累月,量變引起質變的結果。

    attr: function(elem, name, value) {
        var fix = {
            "for": "htmlFor",
            "class": "className",
            "float": "cssFloat",
            innerHTML: "innerHTML",
            className: "className",
            value: "value",
            disabled : "disabled"
        };
        if (fix[name]) {
            if (value != undefined) elem[fix[name]] = value;
            return elem[fix[name]];
        } else if ( elem.getAttribute ) {
            if (value != undefined ) elem.setAttribute( name, value);
            return elem.getAttribute(name, 2);
        } else {
            name = name.replace(/-([a-z])/ig,function(z,b){return b.toUpperCase();});
            if (value != undefined ) elem[name] = value;
            return elem[name]
        }
    },

這個方法不斷膨脹,加入prototype.js發掘出來的特殊屬性處理,以及社區補丁。jquery1.5.2,這個attr方法已經接近100行。
在1.6時,把1.5版本css模塊想出的好方法,cssHooks適配器移植過來,解決了擴展的難題

jquery1.6中存在4個適配器,從單詞直接移過來,就是formHooksattrHooks,propHooks,valHooks。formHook是在attr方法對付舊版本的IE的form元素用到的。到jQuery1.61.新增一個boolHook對付布爾屬性值。

jquery1.6.3,人們發現ie大多數狀況使用getAttributeNode,就能取到正確值,所以對formHook重構下。改名爲nodeHooks,便造成今天的jQuery的屬性系統。如圖

 

上圖爲jQuery的屬性系統概述圖,新生的prop方法異常簡單,複雜些都移動到鉤子。
古老的attr方法則無比複雜,兼任讀,寫,刪三職,因爲IE的狀況不得不動用到3個鉤子(鉤子只處理有問題的屬性),不處理通常狀況。

    //jQuery1.83
    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) { //若是是HTML文檔的元素節點
            name = jQuery.propFix[name] || name;
            hooks = jQuery.propHooks[name];
        }

        if (value !== undefined) { //寫方法
            if (hooks && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) {
                return ret; //處理特殊狀況
            } else { //處理通用狀況
                return (elem[name] = value ); 
            }

        } else { //讀方法
            if (hooks && "get" in hooks && (ret = hooks.get(elem, name)) !== null) {
            return ret;//處理特殊狀況    
            } else { //處理通用狀況
                return elem[name];
            } 
        }
    } ,
    attr: function (elem, name, value) {
        var ret, hooks, notxml, nType = elem.nodeType;
        if (!elem || nType === 3 || nType === 8 || nType === 2) {
            return;// 跳過註釋節點,文本節點,特性節點
        }

        if ( typeof elem.getAttribute === "undefined") {
            return jQuery.prop(elem, name, value); //文檔與window.只能使用prop
        }

        notxml = nType !== 1 || !jQuery.isXMLDoc(elem);
        if (notxml) { //若是是HTML文檔元素節點
            name = name.toLowerCase();//決定用哪個鉤子
            hooks = jQuery.attrHooks[name] || (rboolean.test( name ) ? boolHook : nodeHook);
        }

        if (value !== undefined) {
            if (value === null) { //模仿prototypejs移除屬性
                jQuery.removeAttr(elem, name);
            } else if (hooks && notxml && "set" in hooks && (ret = hooks.set(elem, value, name)) !== undefined) {
                return ret;// 處理特殊狀況
            } else { //處理通用
                elem.setAttribute(name, value + "");
                return value;
            }
        } else if (hooks && notxml && "get" in hooks && (ret = hooks.get(elem, name)) !== null) {
            return ret;//處理特殊狀況
        } else { //處理通用狀況
            ret = elem.getAttribute( name );
            return ret === null ? undefined : ret;
        }
    },

 

四:value的操做

之因此將它獨立出來,是由於它很是重要,涉及與後端的交互。而value值,通常而言,只有表單元素的value纔對咱們有用。下面簡單來講每一個表單元素的狀況

select元素,它的value值就是被選中的option孩子的value值。不過,select有兩種形態,一種type爲select-one,令一種爲select-multiple,就是當用戶設置了multiple屬性。在多選的形態下。咱們可使用ctrl鍵進行多選。

option元素,它的value值能夠是value屬性的值,也能夠是其中間的文本,換言之就是innrtText,當用戶沒有顯式的設置value屬性時,它就取innertext,不過這個innerText要使用text屬性來取。就像script標籤同樣。有人會問,爲何不使用innerHTML呢?由於這個option元素的text屬性比innerHTML多一個trim操做,去掉兩邊的空白(舊版本的IE的innerHTML會作trim操做,標準瀏覽器不會)。

button元素,它的取值與option元素有點相似又不盡相同,在ie6 7中,它取元素的innerText,在IE8時,它才與其它瀏覽器保持一致,取value屬性的值。

checkbox,radio在設置value時,應該考慮對checked屬性的修改。
所以在val方法對應的適配器內,應該有六組方法,對select,option的特殊處理,對button的兼容處理,對checkbox,radio的check值處理,最後是默認值。

關於valhooks請關注業內更多的處理方法。此處僅作備註。

 (本章結束

上一章: 第九章:樣式模塊  下一章:事件系統(onXXX等缺陷)

相關文章
相關標籤/搜索