Dojo 包含两个选择器 lite.js 和 acme.js,lite只是一个基础的选择器,包含id,className,tagName,以及css2.0的属性选择器, 文件库相对于acme.js来说小很多,常用的选择方法都已经覆盖, 而acme.js 比较全面,支持全部的css3.0的要求, 但文件很大
Dojo中还支持 sizzle,可以通过selectorEngine 来自定义选择器, 关于选择器的使用可以参考http://dojotoolkit.org/reference-guide/1.10/dojo/query.html
以下是对 lite.js 源码注释
/* 选择器分为两部分: 拥有 querySelectorAll的浏览器 正则表达式判断出 #id,.class,input等,不是 css格式的选择符 分别调用 getElementById,getElementsByClassName,getElementsByTagName 对于没有getElementsByClassName的浏览器IE8,则调用querySelectorAll 没有符合正则表达式, css 的选择器字符串 "div#id .a" 直接调用 querySelectorAll 函数 没有 querySelectorAll的浏览器 IE6,IE7 如果查询字符串中包含逗号分隔 "div,input" 调用combine方法,分隔成单独的查询字符串,在执行以下的步骤 正则表达式会匹配 div#clock .aa i[id='test'],div,clock,.aa i[id='test'],先查找 #clock元素,然后设用 .aa i[id='test'],#clock,变为 .aa i[id='test'],.aa,i,[id='test'] 正则表达式匹配 .aa i[id='test'] 先在root里找getElementsByTagName('i'),构建新的selector, .aa [id='test'] 调用 jsMatchesSelector,先匹配getElementsByTagName中的元素 其属性是否为[id='test'],然后他的父元素是否有 .aa类 其它 构造 match 类方法 调整 querySelectorAll 方法 */ define(["../has","../_base/kernel"],function(has,dojo){ "use strict"; //test var testDiv = document.createElement("div"); var matchesSelector = testDiv.matches || testDiv.webkitMatchesSelector || testDiv.mozMatchesSelector || testDiv.msMatchesSelector || testDiv.oMatchesSelector; //IE9以下才支持,testDiv.matches('div') 如果每个元素跟css选择器相同,返回ture,否则返回false var querySelectorAll = testDiv.querySelectorAll; var unionSplit = /([^\s,](?:"(?:\\.|[^"])+"|'(?:\\.|[^'])+'|[^,])*)/g; has.add("dom-matches-selector",!!matchesSelector); has.add("dom-qsa",!!querySelectorAll); // this is a simple query engine. It has handles basic selectors,and for simple // common selectors is extremely fast var liteEngine = function(selector,root){ // summary: // A small lightweight query selector engine that implements CSS2.1 selectors // minus pseudo-classes and the sibling combinator,plus CSS3 attribute selectors // 当 query('div,p") 以及 IE7及以下,才调用combine方法 if(combine && selector.indexOf(',') > -1){ return combine(selector,root); } // use the root's ownerDocument if provided,otherwise try to use dojo.doc. Note // that we don't use dojo/_base/window's doc to reduce dependencies,and // fallback to plain document if dojo.doc hasn't been defined (by dojo/_base/window). // presumably we will have a better way to do this in 2.0 /* 如果浏览器提供了node.ownerDocument属性(IE6.0引进,其它未知),doc为拥有这个元素的document对像. */ var doc = root ? root.ownerDocument || root : dojo.doc || document,/* 针对现在浏览器:可以匹配div#id,div.class,input,但对于 span i等, match 返回空 div#id 返回["div.id","div","id"] div.class 返回["div.class",undefined",".","class",undefined] query(".red") 返回 [".red",undefined,"red",undefined] query("div#id .red") 查找div#id 的 .red子元素 querySelectorAl ? null : div#clock1 .red,clock1,.red,数组的格式是 ['匹配到的字符串','div','id','.','className','element'] */ match = (querySelectorAll ? /^([\w]*)#([\w\-]+$)|^(\.)([\w\-\*]+$)|^(\w+$)/ : // 简单的查询, div#id,class, 元素input /* 每一个匹配部分, 都会用于查询的手动过滤 ^([\w]*)#([\w\-]+)(?:\s+(.*))?$ 用于匹配 "div#id .red" 或者 "div#id input" (?:^|(>|.+\s+))([\w\-\*]+)(\S*$) div > ipnut > a:visit result: ["div > ipnut > a:visited","div > ipnut > ","a",":visited"] 返回的数组格式是 ['匹配到的字符串','.class elementName','div>input>','a',":visited'] */ /^([\w]*)#([\w\-]+)(?:\s+(.*))?$|(?:^|(>|.+\s+))([\w\-\*]+)(\S*$)/) // this one matches parts of the query that we can use to speed up manual filtering .exec(selector); root = root || doc; if(match){ // fast path regardless of whether or not querySelectorAll exists if(match[2]){ // an #id // use dojo.byId if available as it fixes the id retrieval in IE,note that we can't use the dojo namespace in 2.0,but if there is a conditional module use,we will use that // IE 的getElementById bug是, name 和 id不分 var found = dojo.byId ? dojo.byId(match[2],doc) : doc.getElementById(match[2]); if(!found || (match[1] && match[1] != found.tagName.toLowerCase())){ // if there is a tag qualifer and it doesn't match,no matches return []; } if(root != doc){ // there is a root element,make sure we are a child of it 如果指定了root, 那么确保root是这个元素的父元素 var parent = found; while(parent != root){ parent = parent.parentNode; if(!parent){ return []; } } } // 对于没有querySelectAll的浏览器, “div#id .red”, 现在只是找到了div#id元素, 还需要在次查找.red元素, 并在找查.red元素时,设置root为div#id; return match[3] ? liteEngine(match[3],found) : [found]; } if(match[3] && root.getElementsByClassName){ // a .class IE8,IE7 不支持 return root.getElementsByClassName(match[4]); } var found; if(match[5]){ // a tag 现代化浏览器,直接根据tagName查找,并返回 found = root.getElementsByTagName(match[5]); // IE7及以下的浏览器,选查找到元素,并构造新的选择器字符串 /* #clock\'1 .aa i[id='test'],#clock\'1 .aa,[id='test'] #clock\'1 .aa [id='test'] */ if(match[4] || match[6]){ selector = (match[4] || "") + match[6]; }else{ // that was the entirety of the query,return results return found; } } } if(querySelectorAll){ // qSA works strangely on Element-rooted queries // We can work around this by specifying an extra ID on the root // and working up from there (Thanks to Andrew Dupont for the technique) // IE 8 doesn't work on object elements 不支持在<object> 标签调用querySelectorAll <object width="400" height="400" data="helloworld.swf"></object> if (root.nodeType === 1 && root.nodeName.toLowerCase() !== "object"){ return useRoot(root,selector,root.querySelectorAll); }else{ // we can use the native qSA // 应用于document // IE8 不能应用此方法 object elements return root.querySelectorAll(selector); } }else if(!found){ // 对于没有querySelectorAll的浏览器 // search all children and then filter found = root.getElementsByTagName("*"); } // now we filter the nodes that were found using the matchesSelector var results = []; for(var i = 0,l = found.length; i < l; i++){ var node = found[i]; if(node.nodeType == 1 && jsMatchesSelector(node,root)){ // keep the nodes that match the selector results.push(node); } } return results; }; var useRoot = function(context,query,method){ // this function creates a temporary id so we can do rooted qSA queries,this is taken from sizzle var oldContext = context,old = context.getAttribute("id"),nid = old || "__dojo__",hasParent = context.parentNode,relativeHierarchySelector = /^\s*[+~]/.test(query); // query('+input',#form1); 相对于context的元素 /* prev + next,返回找到的next元素,next是prev的后一个兄弟元素,prev ~ siblings, 返回prev之后的兄弟元素 <label> <input> <span> <input> label + input 返回 第一个input label ~ input 返回 两个input,span不返回 */ if(relativeHierarchySelector && !hasParent){ //这个表达式应该没有用,因为传递给useRoot不可能为Document对像,只可能是Element, 而Element对像都是有parentNode return []; } if(!old){ context.setAttribute("id",nid); }else{ nid = nid.replace(/'/g,"\\$&"); // $&引用与 regexp 相匹配的子串, $` '左侧的文本 $' '右侧的文本 } if(relativeHierarchySelector && hasParent){ context = context.parentNode; } //对query中的每个元素定义id, query('+ span,~span",context); var selectors = query.match(unionSplit); for(var i = 0; i < selectors.length; i++){ selectors[i] = "[id='" + nid + "'] " + selectors[i]; } /* <div id="tes't"><span><i></i></span></div> 那么: query('span i',document.getElementById("test'1")) 会返回 [id='tes\'t'] span i 在div.parentNode上调用 querySelectorAll("[id='tes\'t'] span i") 查找对应的元素 如果你在元素上直接调用 div.querySelectorAll('body div span i") 这个也是会返回 i 元素,因为querySelectorAll是从整个文档查询 div span i, 然后在过滤出 div#test't内的 span i. useRoot 会让 div.querySelectAll("body div span i") 返回 [],相等于查询 [id='tes\'t'] body div span i */ query = selectors.join(","); try{ return method.call(context,query); }finally{ if(!old){ oldContext.removeAttribute("id"); } } }; if(!has("dom-matches-selector")){ var jsMatchesSelector = (function(){ // a JS implementation of CSS selector matching,first we start with the varIoUs handlers var caseFix = testDiv.tagName == "div" ? "toLowerCase" : "toUpperCase"; //xml 或者 xhtml 会保留原始的大小写, 而html中,都是大小的 var selectorTypes = { "": function(tagName){ tagName = tagName[caseFix](); return function(node){ return node.tagName == tagName; }; },".": function(className){ var classNameSpaced = ' ' + className + ' '; //如果要查询,.aa,而有一个元素的类名是 aaSuffix,对过添加左右空格,就能排除这种情况 return function(node){ return node.className.indexOf(className) > -1 && (' ' + node.className + ' ').indexOf(classNameSpaced) > -1; }; },"#": function(id){ return function(node){ return node.id == id; }; } }; var attrComparators = { "^=": function(attrValue,value){ return attrValue.indexOf(value) == 0; },"*=": function(attrValue,value){ return attrValue.indexOf(value) > -1; },"$=": function(attrValue,value){ return attrValue.substring(attrValue.length - value.length,attrValue.length) == value; },"~=": function(attrValue,value){ return (' ' + attrValue + ' ').indexOf(' ' + value + ' ') > -1; },"|=": function(attrValue,value){ return (attrValue + '-').indexOf(value + '-') == 0; },"=": function(attrValue,value){ return attrValue == value; },"": function(attrValue,value){ return true; } }; function attr(name,value,type){ var firstChar = value.charAt(0); if(firstChar == '"' || firstChar == "'"){ // it is quoted,remove the quotes value = value.slice(1,-1); } value = value.replace(/\\/g,''); var comparator = attrComparators[type || ""]; return function(node){ var attrValue = node.getAttribute(name); return attrValue && comparator(attrValue,value); }; } function ancestor(matcher){ return function(node,root){ while((node = node.parentNode) != root){ if(matcher(node,root)){ return true; } } }; } function parent(matcher){ return function(node,root){ node = node.parentNode; return matcher ? node != root && matcher(node,root) : node == root; }; } var cache = {}; function and(matcher,next){ return matcher ? function(node,root){ return next(node) && matcher(node,root); } : next; } return function(node,root){ // this returns true or false based on if the node matches the selector (optionally within the given root) var matcher = cache[selector]; // check to see if we have created a matcher function for the given selector if(!matcher){ // create a matcher function for the given selector // parse the selectors /* 正则表达式翻译 1. 检测字符串中的 >或者空格,并记录到 &1 2. 检测字符串中是否有 #或者. ,并记录到 &2, 并记录它们后面的字符 &3 (转义字符,eg. id="first.last", 查找时#first\\.last",其它可包含的字符为 [\w-] 3. 检测字符串中是否有 [],并记录 attrName 到 &4,attrType(*=,|=,=,^=)到&5,对于 attrValue有三种类型 "aa",aa,aa ([^\]]) 检测非"]" 结束的字符 */ /* e.g: query("#clock\\'1 .aa i[id='test']") match: #clock\'1 .aa i[id='test'],[id='test'] selector: #clock\'1 .aa [id='test'] 执行 selector.replace 1st matcher = function(node){ return node.id == id; }; 2st matcher = ancestor(matcher) // 返回一个函数 function(node,root){},返回的这个函数会调用 1st中的函数 3st matcher = function(className){ var classNameSpaced = ' ' + className + ' '; //如果要查询,.aa,对过添加左右空格,就能排除这种情况 return function(node){ return node.className.indexOf(className) > -1 && (' ' + node.className + ' ').indexOf(classNameSpaced) > -1; }; } && matcher(node,root) // 调用第二步的matcher 4st 同2st 5st matcher = attr() && matcher (4st) 6 当调用 matcher时, 先调用执行 attr() -> matcher (4st) -> matcher(3st) -> matcher(2st) -> matcher(1st) */ if(selector.replace(/(?:\s*([> ])\s*)|(#|\.)?((?:\\.|[\w-])+)|\[\s*([\w-]+)\s*(.?=)?\s*("(?:\\.|[^"])+"|'(?:\\.|[^'])+'|(?:\\.|[^\]])*)\s*\]/g,function(t,combinator,type,attrName,attrType,attrValue){ if(value){ matcher = and(matcher,selectorTypes[type || ""](value.replace(/\\/g,''))); } else if(combinator){ matcher = (combinator == " " ? ancestor : parent)(matcher); } else if(attrName){ matcher = and(matcher,attr(attrName,attrValue,attrType)); } return ""; })){ throw new Error("Syntax error in query"); } if(!matcher){ return true; } cache[selector] = matcher; } // now run the matcher function on the node return matcher(node,root); }; })(); } /* IE7及以下的浏览器,没有定义querySelectorAll方法 lite 支持的css2 选择器用法 http://dojotoolkit.org/reference-guide/1.10/dojo/query.html */ if(!has("dom-qsa")){ var combine = function(selector,root){ // combined queries //var unionSplit = /([^\s,])*)/g; /* 1. "div,a" 或才 " div,a",[^\s,]以非空格,逗号开头,|[^,]* 当遇到逗号时,停止这次匹配 2. \\.|[^"] 用来匹配转义后的字符,如"aaa\\"bbbbccc",我要匹配整个字任串时,如果只用[^"]只能匹配到"aaa\\" , 为了提升正则表达式的效率,可以改成以下方式 "(?: [^"\\]|\\.)+" 因为 正常字符的出现,要比转义出现的概率大, 所以把 [^"\]提前 3. /([^\s,]([^,])*)/g 但是必须要考虑双引号或者单引号之间有的逗号 4. 如果遇到双引号,就应当一次性的读取“”之间的内容,内容的形式为 [^"] 和 \\. 转义字符,querySelectAll(".class:test") //错误 e.g: query("div,a"); query("div[id='my\\:name']") <div id="my:name"></div> query("[data-name='hello,world']") */ var selectors = selector.match(unionSplit); var indexed = []; // add all results and keep unique ones,this only runs in IE,so we take advantage // of known IE features,particularly sourceIndex which is unique and allows us to // order the results for(var i = 0; i < selectors.length; i++){ selector = new String(selectors[i].replace(/\s*$/,'')); selector.indexOf = escape; // keep it from recursively entering combine var results = liteEngine(selector,root); for(var j = 0,l = results.length; j < l; j++){ var node = results[j]; indexed[node.sourceIndex] = node; } } // now convert from a sparse array to a dense array 将一个稀疏数组变为稠密数组 var totalResults = []; for(i in indexed){ totalResults.push(indexed[i]); } return totalResults; }; } liteEngine.match = matchesSelector ? function(node,root){ if(root && root.nodeType != 9){ // doesn't support three args,use rooted id trick return useRoot(root,function(query){ return matchesSelector.call(node,query); }); } // we have a native matchesSelector,use that return matchesSelector.call(node,selector); } : jsMatchesSelector; // otherwise use the JS matches impl return liteEngine; });