JavaScript實現DOM對象選擇器

目的:node

根據傳入的選擇器類型選出第一個符合的DOM對象。git

①能夠經過id獲取DOM對象,例如 $("#adom");
②能夠經過tagName獲取DOM對象,例如 $("a");
③能夠經過樣式名稱獲取DOM對象,例如 $(".classa");
④能夠經過attribute匹配獲取DOM對象,例如 $("[data-log]"),$("[data-time=2015]");
⑤能夠經過層疊組合獲取DOM對象,例如 $("#adom .classa"); 
 
思路:
須要區分複合選擇仍是單項選擇,單項選擇的話分別用各自的方法進行獲取,複合選擇的話就要進行篩選。
因此 第一步,區分是單項仍是組合。
實現方法是將傳入選擇器的字符串轉換成數組,若是數組長度大於1的話,就是複合選擇。若是不是的話,再判斷是哪種單項選擇器。
if(trim(selector).split(" ").length > 1){ //trim()方法用於去除字符串開頭和結尾的空白
//複合選擇器代碼
}
//判斷是哪種單項選擇器

第二步,判斷是哪種單項選擇器,而後進行篩選返回第一個元素。github

①判斷,有兩種方法:正則表達式

  • 方法一:用正則表達式。
if(/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
      //ID選擇器
}
if(/^((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
     //Tag選擇器
}
if(/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
     //class選擇器
}
if(/^\[[A-Za-z0-9_-\S]+\]$/.test(selector)){
    //屬性選擇器
}
  • 方法二:檢查傳入選擇器的第一個字符
var type=trim(selector).charAt(0);
switch(type){
    case ".":
        //class選擇器
    case "#":
        //id選擇器
    case "[":
        //屬性選擇器
    default:
        //tag選擇器
}

②根據選擇器進行篩選。spring

  • id和tag直接用DOM方法就能夠了。
  • class的document.getElementsByClassName有兼容問題,須要爲IE定義方法。
  • 屬性選擇器須要遍歷全部的DOM節點對象,選擇出符合條件的。
    //ID選擇器
    return document.getElementById(selector.slice(1,selector.length));
    //tag選擇器
    return document.getElementsByTagName(selector)[0];
    //類選擇器
    if(document.getElementsByClassName){
        return document.getElementsByClassName(selector.slice(1,selector.length))[0];
    }else{
        var nodes = document.all ? document.all : document.getElementsByTagName('*');
        for(var i=0;i<nodes.length;i++){
            var classes=nodes[i].className.split(/\s+/);
                if(classes.indexOf(selector.slice(1))!=-1){ //indexOf不兼容,須要在原型上擴展
                    return nodes[i];
                    break;
                } 
            }
        }    
    }
    //屬性選擇器
    if(/^\[[A-Za-z0-9_-\S]+\]$/.test(selector)){
        selector = selector.slice(1,selector.length-1);
        var eles = document.getElementsByTagName("*");
        selector = selector.split("=");
        var att = selector[0];
        var value = selector[1];
        if (value) {
            for (var i = 0; i < eles.length; i++) {
                if(eles[i].getAttribute(att)==value){
                    return eles[i];
                } 
            }
        }else{
            for (var i = 0; i < eles.length; i++) {
                if(eles[i].getAttribute(att)){
                    return eles[i];
                } 
            }
        }
    }

     

第三步,實現複雜選擇器。數組

  • 思路一:

最終篩選出的DOM對象必定是知足最後一個選擇器的DOM對象集合之一,因此能夠先選出這些對象,而後逐個檢查他的祖先元素,是否符合上一層選擇器,不符合的話就刪掉。一直迭代到最外一層選擇器,剩下的DOM對象集合中的第一個就是咱們要找的DOM對象。dom

那麼,若是有n個選擇器,就須要進行n-1輪篩選。函數

這裏須要作兩件事情,①檢查元素的祖先元素是不是選擇器對象集合之一。②檢查對象集合中的每一個元素,刪掉不符合條件的DOM對象。性能

定義兩個函數來作這兩件事:
//遞歸檢查ele的祖先對象是否符合選擇器
function isParent(ele,str){
    if (!isArray(str)) {        //若是不是數組
        str = toArray(str);   //轉換成數組
    }
    if (ele.parentNode) {
        if (str.indexOf(ele.parentNode)>-1) {
            return true;
        }else{
            return isParent(ele.parentNode,str); 
        }
    }else{
        return false;
    }
}
//從eles中刪掉祖先對象不符合選擇器的對象
function fliterEles(eles,str){
    if(!isArray(eles)){
            eles = toArray(eles);
    }
    for (var i = 0,len=eles.length;i<len; i++) {
        if (!isParent(eles[i],str)) {
            eles.splice(i,1);
            i = i - 1;
        }
    }
    return eles;
}

這個實現會有一個BUG,就是當HTML是下面這樣的時候,他會篩選出「第一個」,然而它並非咱們期待的。this

 

雖然實際應用中不多會這樣給父元素和子元素定義相同的class名,但咱們不能忽略這個BUG的存在。

這個實現的性能也是不好的,由於當他檢查對象集合中的一個對象的祖先元素是否符合一個選擇器時,他先檢查他的父元素,不知足的話再檢查他父元素的父元素,一直到沒有父元素爲止。而後他還須要檢查是否符合下一個選擇器,這樣他又遍歷了一遍他的父元素。這裏有重複訪問的地方。

思路一的全部代碼:

//須要一個能夠選擇全部元素的方法
function getElements(selector){
    //類選擇器,返回所有項
    if(/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
        if(document.getElementsByClassName){
            return document.getElementsByClassName(selector.slice(1,selector.length));
        }
        var nodes = document.all ? document.all : document.getElementsByTagName('*');
        var arr=[];  //用來保存符合的className;    
        for(var i=0;i<nodes.length;i++){
            if(hasClass(nodes[i],selector.slice(1,selector.length))){
                arr.push(nodes[i]);
            }
        }
        return arr;
    }

    //ID選擇器
    if(/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
        return document.getElementById(selector.slice(1,selector.length));
    }

    //tag選擇器
    if(/^((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
        return document.getElementsByTagName(selector);
    }

    //屬性選擇器
    if(/^\[[A-Za-z0-9_-\S]+\]$/.test(selector)){
        selector = selector.slice(1,selector.length-1);
        var eles = document.getElementsByTagName("*");
        selector = selector.split("=");
        var att = selector[0];
        var value = selector[1];
        var arr = []; 
        if (value) {
            for (var i = 0; i < eles.length; i++) {
                if(eles[i].getAttribute(att)==value){
                   arr.push(eles[i]);
                } 
            }
        }else{
            for (var i = 0; i < eles.length; i++) {
                if(eles[i].getAttribute(att)){
                    arr.push(eles[i]);
                } 
            }
        }
        return arr;
    }
}

//檢查ele的祖先對象是否符合選擇器
function isParent(ele,str){
    if (!isArray(str)) {
        str = toArray(str);
    }
    if (ele.parentNode) {
        if (str.indexOf(ele.parentNode)>-1) {
            return true;
        }else{
            return isParent(ele.parentNode,str); 
        }
    }else{
        return false;
    }
}

//從eles中刪掉祖先對象不符合選擇器的對象
function fliterEles(eles,str){
    if(!isArray(eles)){
            eles = toArray(eles);
    }
    for (var i = 0; i < eles.length; i++) {
        if (!isParent(eles[i],str)) {
            eles.splice(i,1);
            i = i - 1;
        }
    }
    return eles;
}


//DOM元素選擇器
function $(selector){
    if(!typeof selector === "string"){
        return false;
    }

    //複合選擇器
    if(trim(selector).split(" ").length > 1){
        var all = trim(selector).split(" ");
        var eles = getElements(all[all.length-1]);
        for(var i = 2 ; i < all.length+2 && all.length-i >=0; i++){
            eles = fliterEles(eles,getElements(all[all.length-i]));
        }
        return eles[0];
    }


    //ID選擇器
    if(/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
        return document.getElementById(selector.slice(1,selector.length));
    }


    //tag選擇器,只返回第一個
    if(/^((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
        return document.getElementsByTagName(selector)[0];
    }

    //類選擇器
    if(/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){
        if(document.getElementsByClassName){
            return document.getElementsByClassName(selector.slice(1,selector.length))[0];
        }
        var nodes = document.all ? document.all : document.getElementsByTagName('*');
        for(var i=0;i<nodes.length;i++){
            if(hasClass(nodes[i],selector.slice(1,selector.length))){
                return nodes[i];
            }
        }    
    }


    //屬性選擇器
    if(/^\[[A-Za-z0-9_-\S]+\]$/.test(selector)){
        selector = selector.slice(1,selector.length-1);
        var eles = document.getElementsByTagName("*");
        selector = selector.split("=");
        var att = selector[0];
        var value = selector[1];
        if (value) {
            for (var i = 0; i < eles.length; i++) {
                if(eles[i].getAttribute(att)==value){
                    return eles[i];
                } 
            }
        }else{
            for (var i = 0; i < eles.length; i++) {
                if(eles[i].getAttribute(att)){
                    return eles[i];
                } 
            }
        }
    }
}

 

  • 思路二:

從最外層向裏面篩選。

先從document選出符合最外層選擇器的對象集,目標對象必定是這個對象集的一個對象的子孫元素。
因此,遍歷這個對象集中的每一個元素,從中選出符合第二個選擇器的對象集,而後再遍歷新的對象集。
直到篩選完最後一個選擇器,剩下的對象集中的第一個就是目標對象。
這個方法不須要區分符合選擇器和單項選擇器,也不須要從新定義得到全部元素的方法。
function $(selector){
    var all=selector.split(/\s+/);
    var result = [],rooot=[document];
    for (var i = 0; i < all.length; i++) {
        var type=all[i][0];
        switch(type){
        //ID
        case "#" :
            for (var j = 0; j < rooot.length; j++) {
                var ele=rooot[j].getElementById(all[i].slice(1));
                if (ele) {
                    result.push(ele);
                }
            }
            break;
        
        //class
        case ".":
            for (var j = 0; j < rooot.length; j++) {
                if (document.getElementsByClassName) {
                    var eles=rooot[j].getElementsByClassName(all[i].slice(1));
                    if (eles) {
                        result=result.concat(Array.prototype.slice.call(eles));
                    }
                }else{
                    var arr = rooot[j].getElementsByTagName("*");
                    for (var i = 0; i < arr.length; i++) {
                        if (hasClass(arr[i], className)) {
                            result.push(arr[i]);
                        }
                    }
                }
            }
            break;
        //屬性
        case "[":
            var att = all[i].slice(1,all[i].length-1).split("=");
            var key = att[0],value=att[1];
            for (var j = 0; j < rooot.length; j++) {
                var eles=rooot[j].getElementsByTagName("*");
                for (var i = 0; i < eles.length; i++) {
                    if (value) {
                        for (var i = 0; i < eles.length; i++) {
                            if(eles[i].getAttribute(key)==value){
                                result.push(eles[i]);
                            }
                        }
                    }else{
                        for (var i = 0; i < eles.length; i++) {
                            if(eles[i].getAttribute(key)){
                                result.push(eles[i]);
                            }
                        }
                    }
                }
            }
            break;
        //tag
        default:
            for (var j = 0; j < rooot.length; j++) {
                eles=rooot[j].getElementsByTagName(all[i]);
                if (eles) {
                  result=result.concat(Array.prototype.slice.call(eles));
                }
            }
        }//switch
        rooot=result;
        result=[];   
    }//for
    return rooot[0];
}

用到的公共方法:

//IE9-不支持數組的indexOf()
if (!Array.prototype.indexOf) {
    Array.prototype.indexOf=function(value){
        for (var i = 0,len=this.length;i<len; i++) {
            if(this[i]==value){
                return i;
            }
        }
        return -1;
    };
}

//檢查ele是否有className
function hasClass(ele,className){
    if (ele&&ele.className) {
        var classes=ele.className.split(/\s+/);//這裏必需要切成數組以後再判斷
        if(classes.indexOf(className)!=-1){
            return true;
        } 
    }
    return false;
}

// 判斷arr是否爲一個數組,返回一個bool值
function isArray(arr){
    return Array.isArray(arr)||Object.prototype.toString.call(arr) === "[object Array]";
}

// 對字符串頭尾進行空格字符的去除、包括全角半角空格、Tab等,返回一個字符串
function trim(str){
    return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"")
}

//把一個類數組轉換成數組
function toArray(obj){
    if (obj.nodeType == 1 ) {
        return [obj];
    }
    var arr = [];
    for( var i = 0 ; i < obj.length ; i++){
        arr.push(obj[i]);
    }
    return arr;
}

 

參考:

https://github.com/baidu-ife/ife/blob/master/2015_spring/task/task0002/review/demo/js/util_demo.js

https://github.com/starkwang/ife/blob/master/task/task0002/work/starkwang/js/util.js

相關文章
相關標籤/搜索