目的:node
根據傳入的選擇器類型選出第一個符合的DOM對象。git
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選擇器 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]; } } } } }
從最外層向裏面篩選。
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