原生的强大DOM选择器querySelector介绍
在传统的JavaScript开发中,查找DOM往往是开发人员遇到的第一个头疼的问题,原生的JavaScript所提供的DOM选择方法并不多,仅仅局限于通过tag,name,id等方式来查找,这显然是远远不够的,如果想要进行更为精确的选择不得不使用看起来非常繁琐的正则表达式,或者使用某个库。事实上,现在所有的浏览器厂商都提供了querySelector和querySelectorAll这两个方法的支持,甚至就连微软也派出了IE8作为支持这一特性的代表,querySelector和querySelectorAll作为查找DOM的又一途径,极大地方便了开发者,使用它们,你可以像使用CSS选择器一样快速地查找到你需要的节点。
querySelector和querySelectorAll的使用非常的简单,就像标题说到的一样,它和CSS的写法完全一样,对于前端开发人员来说,这是难度几乎为零的一次学习。假如我们有一个id为test的DIV,为了获取到这个元素,你也许会像下面这样:
document.getElementById("test");
现在我们来试试使用新方法来获取这个DIV:
document.querySelector("#test"); document.querySelectorAll("#test")[0];
下面是个小演示:
我是id为test的div感觉区别不大是吧,但如果是稍微复杂点的情况,原始的方法将变得非常麻烦,这时候querySelector和querySelectorAll的优势就发挥出来了。比如接下来这个例子,我们将在document中选取class为test的div的子元素p的第一个子元素,当然这很拗口,但是用本文的新方法来选择这个元素,比用言语来描述它还要简单。
document.querySelector("div.test>p:first-child"); document.querySelectorAll("div.test>p:first-child")[0];
下面是个小演示:
我是层里的p标签
现在应该对于querySelector、querySelectorAll方法中的参数已经非常明白了,是的,它接收的参数和CSS选择器完全一致。querySelector和querySelectorAll的区别在于querySelector用来获取一个元素,而querySelectorAll可以获取多个元素。querySelector将返回匹配到的第一个元素,如果没有匹配的元素则返回Null。querySelectorAll返回一个包含匹配到的元素的数组,如果没有匹配的元素则返回的数组为空。在本文最后一个例子中,我们使用querySelectorAll给所有class为emphasis的元素加粗显示。
varemphasisText=document.querySelectorAll(".emphasis"); for(vari=0,j=emphasisText.length;i<j;i++){ emphasisText[i].style.fontWeight="bold"; }
这是原生方法,比起jquery速度快,缺点是IE6、7不支持。
W3C的规范与库中的实现
querySelector:returnthefirstmatchingElementnodewithinthenode'ssubtrees.Ifthereisnosuchnode,themethodmustreturnnull.(返回指定元素节点的子树中匹配selector的集合中的第一个,如果没有匹配,返回null)
querySelectorAll:returnaNodeListcontainingallofthematchingElementnodeswithinthenode'ssubtrees,indocumentorder.Iftherearenosuchnodes,themethodmustreturnanemptyNodeList.(返回指定元素节点的子树中匹配selector的节点集合,采用的是深度优先预查找;如果没有匹配的,这个方法返回空集合)
这在BaseElement为document的时候,没有什么问题,各浏览器的实现基本一致;但是,当BaseElement为一个普通的domNode的时候(支持这两个方法的domNode),浏览器的实现就有点奇怪了,举个例子:
<divclass="test"id="testId"> <p><span>Test</span></p> </div> <scripttype="text/javascript"> vartestElement=document.getElementById('testId'); varelement=testElement.querySelector('.testspan'); varelementList=document.querySelectorAll('.testspan'); console.log(element);//<span>Test</span> console.log(elementList);//1 </script>
按照W3C的来理解,这个例子应该返回:element:null;elementList:[];因为作为baseElement的testElement里面根本没有符合selectors的匹配子节点;但浏览器却好像无视了baseElement,只在乎selectors,也就是说此时baseElement近乎document;这和我们的预期结果不合,也许随着浏览器的不断升级,这个问题会得到统一口径!
人的智慧总是无穷的,AndrewDupont发明了一种方法暂时修正了这个怪问题,就是在selectors前面指定baseElement的id,限制匹配的范围;这个方法被广泛的应用在各大流行框架中;
Jquery的实现:
varoldContext=context, old=context.getAttribute("id"), nid=old||id,
try{ if(!relativeHierarchySelector||hasParent){ returnmakeArray(context.querySelectorAll("[id='"+nid+"']"+query),extra); } } catch(pseudoError){} finally{ if(!old){ oldContext.removeAttribute("id"); } }
先不看这点代码中其他的地方,只看他如何实现这个方法的;这点代码是JQuery1.6的片段;当baseElement没有ID的时候,给他设置一个id="__sizzle__”,然后再使用的时候加在selectors的前面,做到范围限制;context.querySelectorAll("[id='"+nid+"']"+query;最后,因为这个ID本身不是baseElement应该有的,所以,还需要移除:oldContext.removeAttribute("id");,Mootools的实现:
varcurrentId=_context.getAttribute('id'),slickid='slickid__'; _context.setAttribute('id',slickid); _expression='#'+slickid+''+_expression; context=_context.parentNode;
Mootools和Jquery类似:只不过slickid='slickid__';其实意义是一样的;方法兼容性:FF3.5+/IE8+/Chrome1+/opera10+/Safari3.2+;IE8:不支持baseElement为object;