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", "div", undefined", ".", "class", undefined] query(".red") 返回 [".red", undefined, undefined, ".", "red", undefined] query("div#id .red") 查找div#id 的 .red子元素 querySelectorAl ? null : div#clock1 .red,div,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", undefined, undefined, undefined, "div > ipnut > ", "a", ":visited"] 返回的数组格式是 ['匹配到的字符串', 'div', 'id', '.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 ,i,[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, selector, 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, selector, 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'],,,,#clock\'1 .aa ,i,[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, 而有一个元素的类名是 aaSuffix, 对过添加左右空格,就能排除这种情况 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, value, 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, selector, root){ if(root && root.nodeType != 9){ // doesn't support three args, use rooted id trick return useRoot(root, selector, 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; });