JavaScript實(shí)現(xiàn)DOM對(duì)象選擇器
目的:
根據(jù)傳入的選擇器類(lèi)型選出第一個(gè)符合的DOM對(duì)象。
①可以通過(guò)id獲取DOM對(duì)象,例如 $("#adom");
②可以通過(guò)tagName獲取DOM對(duì)象,例如 $("a");
③可以通過(guò)樣式名稱(chēng)獲取DOM對(duì)象,例如 $(".classa");
④可以通過(guò)attribute匹配獲取DOM對(duì)象,例如 $("[data-log]"),$("[data-time=2015]");
⑤可以通過(guò)層疊組合獲取DOM對(duì)象,例如 $("#adom .classa");
思路:
需要區(qū)分復(fù)合選擇還是單項(xiàng)選擇,單項(xiàng)選擇的話分別用各自的方法進(jìn)行獲取,復(fù)合選擇的話就要進(jìn)行篩選。
所以第一步,區(qū)分是單項(xiàng)還是組合。
實(shí)現(xiàn)方法是將傳入選擇器的字符串轉(zhuǎn)換成數(shù)組,如果數(shù)組長(zhǎng)度大于1的話,就是復(fù)合選擇。如果不是的話,再判斷是哪一種單項(xiàng)選擇器。
if(trim(selector).split(" ").length > 1){ //trim()方法用于去除字符串開(kāi)頭和結(jié)尾的空白 //復(fù)合選擇器代碼 } //判斷是哪一種單項(xiàng)選擇器
第二步,判斷是哪一種單項(xiàng)選擇器,然后進(jìn)行篩選返回第一個(gè)元素。
①判斷,有兩種方法:
•方法一:用正則表達(dá)式。
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)){ //屬性選擇器 }
•方法二:檢查傳入選擇器的第一個(gè)字符
var type=trim(selector).charAt(0); switch(type){ case ".": //class選擇器 case "#": //id選擇器 case "[": //屬性選擇器 default: //tag選擇器 }
②根據(jù)選擇器進(jìn)行篩選。
•id和tag直接用DOM方法就可以了。
•class的document.getElementsByClassName有兼容問(wèn)題,需要為IE定義方法。
•屬性選擇器需要遍歷所有的DOM節(jié)點(diǎn)對(duì)象,選擇出符合條件的。
//ID選擇器 return document.getElementById(selector.slice(1,selector.length)); //tag選擇器 return document.getElementsByTagName(selector)[0]; //類(lèi)選擇器 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不兼容,需要在原型上擴(kuò)展 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]; } } } }
第三步,實(shí)現(xiàn)復(fù)雜選擇器。
•思路一:
最終篩選出的DOM對(duì)象一定是滿足最后一個(gè)選擇器的DOM對(duì)象集合之一,所以可以先選出這些對(duì)象,然后逐個(gè)檢查他的祖先元素,是否符合上一層選擇器,不符合的話就刪掉。一直迭代到最外一層選擇器,剩下的DOM對(duì)象集合中的第一個(gè)就是我們要找的DOM對(duì)象。
那么,如果有n個(gè)選擇器,就需要進(jìn)行n-1輪篩選。
這里需要做兩件事情,①檢查元素的祖先元素是否是選擇器對(duì)象集合之一。②檢查對(duì)象集合中的每個(gè)元素,刪掉不符合條件的DOM對(duì)象。
定義兩個(gè)函數(shù)來(lái)做這兩件事:
//遞歸檢查ele的祖先對(duì)象是否符合選擇器 function isParent(ele,str){ if (!isArray(str)) { //如果不是數(shù)組 str = toArray(str); //轉(zhuǎn)換成數(shù)組 } if (ele.parentNode) { if (str.indexOf(ele.parentNode)>-1) { return true; }else{ return isParent(ele.parentNode,str); } }else{ return false; } } //從eles中刪掉祖先對(duì)象不符合選擇器的對(duì)象 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; }
這個(gè)實(shí)現(xiàn)會(huì)有一個(gè)BUG,就是當(dāng)HTML是下面這樣的時(shí)候,他會(huì)篩選出“第一個(gè)”,然而它并不是我們期待的。
雖然實(shí)際應(yīng)用中很少會(huì)這樣給父元素和子元素定義相同的class名,但我們不能忽略這個(gè)BUG的存在。
這個(gè)實(shí)現(xiàn)的性能也是很差的,因?yàn)楫?dāng)他檢查對(duì)象集合中的一個(gè)對(duì)象的祖先元素是否符合一個(gè)選擇器時(shí),他先檢查他的父元素,不滿足的話再檢查他父元素的父元素,一直到?jīng)]有父元素為止。然后他還需要檢查是否符合下一個(gè)選擇器,這樣他又遍歷了一遍他的父元素。這里有重復(fù)訪問(wèn)的地方。
思路一的所有代碼:
//需要一個(gè)可以選擇所有元素的方法 function getElements(selector){ //類(lèi)選擇器,返回全部項(xiàng) 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=[]; //用來(lái)保存符合的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的祖先對(duì)象是否符合選擇器 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中刪掉祖先對(duì)象不符合選擇器的對(duì)象 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; } //復(fù)合選擇器 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選擇器,只返回第一個(gè) if(/^((?:[\w\u00c0-\uFFFF\-]|\\.)+)/.test(selector)){ return document.getElementsByTagName(selector)[0]; } //類(lèi)選擇器 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選出符合最外層選擇器的對(duì)象集,目標(biāo)對(duì)象一定是這個(gè)對(duì)象集的一個(gè)對(duì)象的子孫元素。
所以,遍歷這個(gè)對(duì)象集中的每個(gè)元素,從中選出符合第二個(gè)選擇器的對(duì)象集,然后再遍歷新的對(duì)象集。
直到篩選完最后一個(gè)選擇器,剩下的對(duì)象集中的第一個(gè)就是目標(biāo)對(duì)象。
這個(gè)方法不需要區(qū)分符合選擇器和單項(xiàng)選擇器,也不需要重新定義獲得所有元素的方法。
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-不支持?jǐn)?shù)組的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+/);//這里必須要切成數(shù)組之后再判斷 if(classes.indexOf(className)!=-1){ return true; } } return false; } // 判斷arr是否為一個(gè)數(shù)組,返回一個(gè)bool值 function isArray(arr){ return Array.isArray(arr)||Object.prototype.toString.call(arr) === "[object Array]"; } // 對(duì)字符串頭尾進(jìn)行空格字符的去除、包括全角半角空格、Tab等,返回一個(gè)字符串 function trim(str){ return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"") } //把一個(gè)類(lèi)數(shù)組轉(zhuǎn)換成數(shù)組 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
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- JavaScript文檔對(duì)象模型DOM
- JavaScript Dom對(duì)象的操作
- JavaScript 中的文檔對(duì)象模型 DOM
- JavaScript變量Dom對(duì)象的所有屬性
- JS實(shí)現(xiàn)訪問(wèn)DOM對(duì)象指定節(jié)點(diǎn)的方法示例
- 淺談JS讀取DOM對(duì)象(標(biāo)簽)的自定義屬性
- js基礎(chǔ)之DOM中document對(duì)象的常用屬性方法詳解
- js基礎(chǔ)之DOM中元素對(duì)象的屬性方法詳解
- JavaScript DOM 對(duì)象深入了解
- JavaScript——DOM操作——Window.document對(duì)象詳解
- jquery對(duì)象和javascript對(duì)象即DOM對(duì)象相互轉(zhuǎn)換
- js對(duì)象關(guān)系圖 方便dom操作
- javascript DOM對(duì)象的學(xué)習(xí)實(shí)例代碼
- JavaScript操作DOM對(duì)象詳解
相關(guān)文章
除Console.log()外更多的Javascript調(diào)試命令
本篇文章給大家介紹了除Console.log()外更多的Javascript調(diào)試命令,方便大家更多環(huán)境下的JS調(diào)試,學(xué)習(xí)下吧。2018-01-01javascript網(wǎng)頁(yè)隨機(jī)點(diǎn)名實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了javascript實(shí)現(xiàn)網(wǎng)頁(yè)隨機(jī)變色及實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-10-10微信小程序 Animation實(shí)現(xiàn)圖片旋轉(zhuǎn)動(dòng)畫(huà)示例
這篇文章主要介紹了微信小程序 Animation實(shí)現(xiàn)圖片旋轉(zhuǎn)動(dòng)畫(huà)示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08Jupyter Notebook運(yùn)行JavaScript的方法
Jupyter Notebook是一塊所見(jiàn)即所得的畫(huà)布,通過(guò)在瀏覽器上編輯代碼,讓開(kāi)發(fā)人員實(shí)現(xiàn)展示與快速迭代的利器,本文主要介紹了Jupyter Notebook運(yùn)行JavaScript的方法,感興趣的可以了解一下2021-05-05JavaScript forEach 方法跳出循環(huán)的操作方法
這篇文章主要介紹了JavaScript forEach 方法跳出循環(huán)的操作方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2024-01-01