Dojo 的css 2.0 lite 选择器源代码

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;
});

相关文章

我有一个网格,可以根据更大的树结构编辑小块数据.为了更容易...
我即将开始开发一款教育性的视频游戏.我已经决定以一种我可以...
我正在使用带有Grails2.3.9的Dojo1.9.DojoNumberTextBox小部...
1.引言鉴于个人需求的转变,本系列将记录自学arcgisapiforja...
我正在阅读使用dojo’sdeclare进行类创建的语法.描述令人困惑...
我的团队由更多的java人员和JavaScript经验丰富组成.我知道这...