复制代码 代码如下:
function $$() {
return Selector.findChildElements(document, $A(arguments));
}
这个类可以分成三个部分:第一个部分就是根据不同的浏览器,判断使用什么DOM操作方法。其中操作IE就是用普通的getElementBy* 系列方法;FF是document.evaluate;Opera和Safari是selectorsAPI。第二部分是对外提供的基本函数,像findElements,match等,Element对象里面的很多方法就是直接调用这个对象里面的方法。第三部分就是XPath等一些查询DOM的匹配标准,比如什么的字符串代表的意思是查找first-child,什么的字符串代表的是查询nth-child。
由于这个对象里面的方法很多,就不给出所有的源码了,其实我自己也仅仅看懂了一些方法的代码而已。这里根据浏览器的不同用一个简单的例子走一遍进行DOM选择的流程。在这个过程中给出需要的源代码,并加以说明。
具体的例子如下:
复制代码 代码如下:
<div>
<div>
<a></a>
<a></a>
</div>
<div>
<a></a>
<a></a>
</div>
</div>
<script type="text/javascript"><!--
$$('#navbar a', '#sidebar a')
// --></script>
下面以FF为例进行说明,流程如下:
复制代码 代码如下:
/*先找到$$方法,上面已经给出了,在这个方法里面将调用Selector的findChildElements方法,并且第一个参数为document,剩下参数为DOM查询字符串的数组*/
findChildElements: function(element, expressions) {
//这里先调用split处理了一下字符串数组,判断是否合法,并且删除了空格
expressions = Selector.split(expressions.join(','));
//handlers里面包含了对DOM节点处理的一些方法,像concat,unique等
var results = [], h = Selector.handlers;
//逐个处理查询表达式
for (var i = 0, l = expressions.length, selector; i < l; i++) {
//新建Selector
selector = new Selector(expressions[i].strip());
//把查询到的节点连接到results里面
h.concat(results, selector.findElements(element));
}
//如果找到的节点数大于一,把重复节点过滤掉
return (l > 1) ? h.unique(results) : results;
}
//===================================================
//Selector.split方法:
split: function(expression) {
var expressions = [];
expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
//alert(m[1]);
expressions.push(m[1].strip());
});
return expressions;
}
//===================================================
//Selector.handlers对象
handlers: {
concat: function(a, b) {
for (var i = 0, node; node = b[i]; i++)
a.push(node);
return a;
},
//...省略一些方法
unique: function(nodes) {
if (nodes.length == 0) return nodes;
var results = [], n;
for (var i = 0, l = nodes.length; i < l; i++)
if (typeof (n = nodes[i])._countedByPrototype == 'undefined') {
n._countedByPrototype = Prototype.emptyFunction;
results.push(Element.extend(n));
}
return Selector.handlers.unmark(results);
},
//下面转向新建Selector对象过程!!
复制代码 代码如下:
//先看Selector的初始化部分
//可以看出初始化部分就是判断要用什么方法操作DOM,下面看一个这几个方法
var Selector = Class.create({
initialize: function(expression) {
this.expression = expression.strip();
if (this.shouldUseSelectorsAPI()) {
this.mode = 'selectorsAPI';
} else if (this.shouldUseXPath()) {
this.mode = 'xpath';
this.compileXPathMatcher();
} else {
this.mode = "normal";
this.compileMatcher();
}
}
//===================================================
//XPath,FF支持此种方法
shouldUseXPath: (function() {
//下面检查浏览器是否有BUG,具体这个BUG是怎么回事,我在网上也没搜到。大概意思就是检查一下能否正确找到某个节点的个数
var IS_DESCENDANT_SELECTOR_BUGGY = (function(){
var isBuggy = false;
if (document.evaluate && window.XPathResult) {
var el = document.createElement('div');
el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>';
//这里的local-name()的意思就是去掉命名空间进行查找
var xpath = ".//*[local-name()='ul' or local-name()='UL']" +
"//*[local-name()='li' or local-name()='LI']";
//document.evaluate是核心的DOM查询方法,具体的使用可以到网上搜
var result = document.evaluate(xpath, el, null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
isBuggy = (result.snapshotLength !== 2);
el = null;
}
return isBuggy;
})();
return function() {
//返回的方法中判断是否支持此种DOM操作。
if (!Prototype.BrowserFeatures.XPath) return false;
var e = this.expression;
//这里可以看到Safari不支持-of-type表达式和empty表达式的操作
if (Prototype.Browser.WebKit &&
(e.include("-of-type") || e.include(":empty")))
return false;
if ((/(\[[\w-]*?:|:checked)/).test(e))
return false;
if (IS_DESCENDANT_SELECTOR_BUGGY) return false;
return true;
}
})(),
//===================================================
//Sarafi和opera支持此种方法
shouldUseSelectorsAPI: function() {
if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
//这里判断是否支持大小写敏感查找
if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false;
if (!Selector._div) Selector._div = new Element('div');
//检查一下在空div里面进行查询是否会抛出异常
try {
Selector._div.querySelector(this.expression);
} catch(e) {
return false;
}
//===================================================
//Selector.CASE_INSENSITIVE_CLASS_NAMES属性
/*document.compatMode用来判断当前浏览器采用的渲染方式。
当document.compatMode等于BackCompat时,浏览器客户区宽度是document.body.clientWidth;
当document.compatMode等于CSS1Compat时,浏览器客户区宽度是document.documentElement.clientWidth。*/
if (Prototype.BrowserFeatures.SelectorsAPI &&
document.compatMode === 'BackCompat') {
Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){
var div = document.createElement('div'),
span = document.createElement('span');
div.id = "prototype_test_id";
span.className = 'Test';
div.appendChild(span);
var isIgnored = (div.querySelector('#prototype_test_id .test') !== null);
div = span = null;
return isIgnored;
})();
}
return true;
},
//===================================================
//如果这两个都不是就用document.getElement(s)By*系列方法进行处理,貌似IE8开始支持SelectorAPI了,其余版本IE就只能用普通的方法进行DOM查询了
//下面转向FF支持的shouldUseXPath方法!!!
复制代码 代码如下: