dojo/dom源碼學習


  dojo/dom模塊作為一個基礎模塊,最常用的就是byId方法。除此之外還有isDescendant和setSelectable方法。

  dom.byId(myId)方法:

    各種前端類庫都免不了與DOM節點打交道,操作DOM的方法千變萬化最終還是要回到原生的那幾個方法中,因為類庫再快也快不過原生。所以在dom.byId方法中,還是要依靠document.getElementById('myId')方法。假如沒有ie,假如不要考慮兼容性,getElementById方法可以完全滿足我們的需求。但是,ie毀了一切,借用美國同事的一句話:Fuck the stupid IE! 兼容性問題有兩條:

  • ie8及較低版本中,myId不區分大小寫,所以myid跟myId會返回同樣的結果
  • ie7及較低版本中,如果name名稱與給定ID相同的表單元素且表單元素在給定ID元素的前面,那么IE就會返回那個表單元素

  這就要求我們在必須判斷一下得到的元素的id是否真與傳入參數相同。判斷方法是利用id屬性或id特性節點:

var te = id && document.getElementById(id)
te && (te.attributes.id.value == id || te.id == id)

  如果上帝為你關上了一扇門,他一定會為你打開另一扇門(好矯情,就行天無絕人之路嘛)。ie4開始提供了document.all,它是一個代表所有元素的集合。document.all[myId]返回id為myId的一個元素或者包含name為myId的一個類數組。我們可以循環判斷其中的元素是否滿足要求:

var eles = document.all[id];
                if(!eles || eles.nodeName){
                    eles = [eles];
                }
                // if more than 1, choose first with the correct id
                var i = 0;
                while((te = eles[i++])){
                    if((te.attributes && te.attributes.id && te.attributes.id.value == id) || te.id == id){
                        return te;
                    }
                }

  所以,dojo/dom中的實現根據瀏覽器的不同,有不同的實現:

    if(has("ie")){
        dom.byId = function(id, doc){
            if(typeof id != "string"){
                return id;
            }
            var _d = doc || win.doc, te = id && _d.getElementById(id);
            // attributes.id.value is better than just id in case the
            // user has a name=id inside a form
            if(te && (te.attributes.id.value == id || te.id == id)){
                return te;
            }else{
                var eles = _d.all[id];
                if(!eles || eles.nodeName){
                    eles = [eles];
                }
                // if more than 1, choose first with the correct id
                var i = 0;
                while((te = eles[i++])){
                    if((te.attributes && te.attributes.id && te.attributes.id.value == id) || te.id == id){
                        return te;
                    }
                }
            }
        };
    }else{
        dom.byId = function(id, doc){
            // inline'd type check.
            // be sure to return null per documentation, to match IE branch.
            return ((typeof id == "string") ? (doc || win.doc).getElementById(id) : id) || null; // DOMNode
        };
    }

  

  dom.isDescendant(node, ancestor)方法:

  這個方法用來判斷node是否是ancestor的一個子節點,其實就是孩子找父親。孩子找父親比較簡單,而父親找孩子是比較難的,因為子節點一定有父節點,所以只要一級一級的找上去即可。

dom.isDescendant = function(/*DOMNode|String*/ node, /*DOMNode|String*/ ancestor){

        try{
            node = dom.byId(node);
            ancestor = dom.byId(ancestor);
            while(node){
                if(node == ancestor){
                    return true; // Boolean
                }
                node = node.parentNode;
            }
        }catch(e){ /* squelch, return false */ }
        return false; // Boolean
    };

  其實還有一個原生的函數也可以滿足要求:element.contains方法,不過這個方法並沒有被納入規范中。但是幾乎所有的瀏覽器都支持,包括IE(最初就是ie增加的該方法,總算做了件好事。。)。所以該方法也可以這樣實現:

dom.isDescendant = function(/*DOMNode|String*/ node, /*DOMNode|String*/ ancestor){
        try{
            node = dom.byId(node);
            ancestor = dom.byId(ancestor);
            return ancestor.contains(node);
        }catch(e){ /* squelch, return false */ }
        return false; // Boolean
    };

 

  dom.setSelectable(node, selectable)方法:

  看名字也知道是用來設置一個節點及其自己點是否可選中的。css屬性中可以通過設置“user-select”來控制一個元素是否可選擇,但這個屬性並未被納入標准中去,所以各個瀏覽器中都需要加瀏覽器前綴,如:-webkit、-moz、-ms、-o等;所以我們可以通過設置元素的style屬性中的相應屬性來控制元素的可選擇性。但是,ie總是太操蛋,大多數情況下,ms前綴都可以解決問題,但是如果一個將一個frame作為編輯器使用時,設置msUserSelect為none時無法達到效果,所以在ie中我們利用unselectable特性來解決這個問題。ie下存在的這個特性:unselectable, 設為on則不可選中,移除這個屬性則表示可選中。

  dojo的實現中,首先判斷userSelect屬性是否能使用:

has.add("css-user-select", function(global, doc, element){
        // Avoid exception when dom.js is loaded in non-browser environments
        if(!element){ return false; }
        
        var style = element.style;
        var prefixes = ["Khtml", "O", "Moz", "Webkit"],
            i = prefixes.length,
            name = "userSelect",
            prefix;

        // Iterate prefixes from most to least likely
        do{
            if(typeof style[name] !== "undefined"){
                // Supported; return property name
                return name;
            }
        }while(i-- && (name = prefixes[i] + "UserSelect"));

        // Not supported if we didn't return before now
        return false;
    });

  這里省略了ms前綴。

  然后根據對"css-user-select"的支持,使用不同的實現:

var cssUserSelect = has("css-user-select");
    dom.setSelectable = cssUserSelect ? function(node, selectable){
        // css-user-select returns a (possibly vendor-prefixed) CSS property name
        dom.byId(node).style[cssUserSelect] = selectable ? "" : "none";
    } : function(node, selectable){
        node = dom.byId(node);

        // (IE < 10 / Opera) Fall back to setting/removing the
        // unselectable attribute on the element and all its children
        var nodes = node.getElementsByTagName("*"),
            i = nodes.length;

        if(selectable){
            node.removeAttribute("unselectable");
            while(i--){
                nodes[i].removeAttribute("unselectable");
            }
        }else{
            node.setAttribute("unselectable", "on");
            while(i--){
                nodes[i].setAttribute("unselectable", "on");
            }
        }
    };

  ie中,循環改變所有子節點的unselectable特性來控制選擇性。

  分享一條小經驗:設置一個元素不可選中時,最好在能滿足需求的最遠祖先上設置,如果僅僅在一個元素上設置未必能夠達到效果;比如:設置一個圖片不可選中,但是祖先可以選中,用戶可能會祖先選中時會變藍,看起來好像圖片依然能夠被選中。

 

  如果您覺得這篇文章對您有幫助,請不吝點擊下方的推薦按鈕,您的鼓勵是我分享的動力!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM