【Zepto源码】选择器引擎(qsa方法)

老姚
老姚 发布于 2016-12-21 09:38:27 浏览:378 类型:原创 - 随笔 分类:JavaScript - zepto源码分析系列 二维码: 作者原创 版权保护
读过zepto的源码的同学都知道,
其css选择器引擎,用的是querySelectorAll方法。

如果要模拟zepto选择器,可以简单使用如下的方式:
<script>
// 选择器模拟
var $ = function query(selector, context) {
	// 稳妥作用域模式,保证是通过new来调用的。
	if (!(this instanceof query)) {
		return new query(selector, context);
	}
	context = context || document;
	var dom = context.querySelectorAll(selector);
	var len = dom ? dom.length : 0;
	for (var i = 0; i < len; i++) {
		this[i] = dom[i];
	}
	this.length = len;
	this.selector = selector;
};
</script>

<div>
	<p>1111</p>
	<p class="target">2222</p>
	<p>3333</p>
	<p class="target">4444</p>
</div>

<script>
var targets = $('div p.target');
alert(targets.length);
</script>

zepto内部实现时,把选择器这块专门提取出一个方法:qsa。可以简单模拟如下:
var $ = function query(selector, context) {
	// 稳妥作用域模式,保证是通过new来调用的。
	if (!(this instanceof query)) {
		return new query(selector, context);
	}
	context = context || document;
	var dom = qsa(context, selector);
	var len = dom ? dom.length : 0;
	for (var i = 0; i < len; i++) {
		this[i] = dom[i];
	}
	this.length = len;
	this.selector = selector;
};

function qsa(element, selector) {
	return element.querySelectorAll(selector);
}

zepto的qsa,虽以querySelectorAll为核心,但是还做了很多优化:
1.如果是id选择器,调用getElementById,模拟如下:
// 单选择器标签符正则
var simpleSelectorRE = /^[\w-]*$/;

function qsa(element, selector) {
	var maybeID = selector[0] == '#';
	var nameOnly = maybeID ? selector.slice(1) : selector;
	var isSimple = simpleSelectorRE.test(nameOnly);
	
	var found;
	
	if (maybeID && isSimple) {
		found = element.getElementById(nameOnly);
		
		// 保证结果是个类数组
		return found ? [found] : [];
	}
	
	return element.querySelectorAll(selector);
}

比如使用$('#first')会走if的逻辑,而$('#container #first'),仍然会使用querySelectorAll。测试案例如下:
<script>
var $ = function query(selector, context) {
	if (!(this instanceof query)) {
		return new query(selector, context);
	}
	context = context || document;
	var dom = qsa(context, selector);
	var len = dom ? dom.length : 0;
	for (var i = 0; i < len; i++) {
		this[i] = dom[i];
	}
	this.length = len;
	this.selector = selector;
};

// 单选择器标签符正则
var simpleSelectorRE = /^[\w-]*$/;

function qsa(element, selector) {
	var maybeID = selector[0] == '#';
	var nameOnly = maybeID ? selector.slice(1) : selector;
	var isSimple = simpleSelectorRE.test(nameOnly);
	
	var found;
	
	if (maybeID && isSimple) {
		found = element.getElementById(nameOnly);
		
		// 保证结果是个类数组
		return found ? [found] : [];
	}
	
	return element.querySelectorAll(selector);
}
</script>

<div id="container">
	<p id="first">1111</p>
	<p class="target">2222</p>
	<p>3333</p>
	<p class="target">4444</p>
</div>

<script>
alert($('#first').length);
alert($('#container #first').length);
</script>

2.如果是类选择器,使用getElementsByClassName,模拟如下:
// 单选择器标签符正则
var simpleSelectorRE = /^[\w-]*$/;

function qsa(element, selector) {
	var maybeClass = selector[0] == '.';
	var nameOnly = maybeClass ? selector.slice(1) : selector;
	var isSimple = simpleSelectorRE.test(nameOnly);
	
	if (maybeClass && isSimple) {
		return element.getElementsByClassName(nameOnly);
	}
	
	return element.querySelectorAll(selector);
}

3.如果是单选择器的其余情形,
那么就认为是标签选择器,比如$('div'),
使用getElementsByTagName,这里不模拟了。

给出qsa的最终合成写法是:
// 单选择器标签符正则
var simpleSelectorRE = /^[\w-]*$/;

function qsa(element, selector) {
	var found, 
		maybeID = selector[0] == '#',
		maybeClass = !maybeID && selector[0] == '.',
		nameOnly = maybeID || maybeClass ? selector.slice(1) : selector,
		isSimple = simpleSelectorRE.test(nameOnly);
	// 是否是id选择器	
	if (element.getElementById && isSimple && maybeID) {
		found = element.getElementById(nameOnly);
		return found ? [found] : [];
	} else {
		// element不是元素类型,不是document,不是文档碎片
		if (element.nodeType !== 1 && element.nodeType !== 9 && element.nodeType !== 11) {
			return [];
		} else {
			if (isSimple && !maybeID && element.getElementsByClassName) {
				// 类选择器
				if (maybeClass) {
					// 返回是个数组
					return [].slice.call(element.getElementsByClassName(nameOnly));
				} else {
					//标签选择器
					return [].slice.call(element.getElementsByTagName(selector));
				}
			} else {
				// 其余
				return [].slice.call(element.querySelectorAll(selector));
			}
		}
	}
}

其实上面的代码,只是把zepto的qsa方法,拿过来改了下形式,其源码是:
// `$.zepto.qsa` is Zepto's CSS selector implementation which
// uses `document.querySelectorAll` and optimizes for some special cases, like `#id`.
// This method can be overridden in plugins.
zepto.qsa = function (element, selector) {
	var found, maybeID = selector[0] == '#',
		maybeClass = !maybeID && selector[0] == '.',
		nameOnly = maybeID || maybeClass ? selector.slice(1) : selector,
		// Ensure that a 1 char tag name still gets checked
		isSimple = simpleSelectorRE.test(nameOnly) 
	return (element.getElementById && isSimple && maybeID) ?// Safari DocumentFragment doesn't have getElementById
		((found = element.getElementById(nameOnly)) ? [found] : []) : 
		(element.nodeType !== 1 && element.nodeType !== 9 && element.nodeType !== 11) ? [] : 
		slice.call(isSimple && !maybeID && element.getElementsByClassName ? // DocumentFragment doesn't have getElementsByClassName/TagName
			maybeClass ? element.getElementsByClassName(nameOnly) : // If it's simple, it could be a class
			element.getElementsByTagName(selector) : // Or a tag
			element.querySelectorAll(selector) // Or it's not simple, and we need to query all
		)
}

希望其源码中的条件表达式的嵌套没有使你头晕脑胀。

最后贴一下,本文完整测试案例如下:
<script>
var $ = function query(selector, context) {
	if (!(this instanceof query)) {
		return new query(selector, context);
	}
	context = context || document;
	var dom = qsa(context, selector);
	var len = dom ? dom.length : 0;
	for (var i = 0; i < len; i++) {
		this[i] = dom[i];
	}
	this.length = len;
	this.selector = selector;
};

// 单选择器标签符正则
var simpleSelectorRE = /^[\w-]*$/;

function qsa(element, selector) {
	var found, 
		maybeID = selector[0] == '#',
		maybeClass = !maybeID && selector[0] == '.',
		nameOnly = maybeID || maybeClass ? selector.slice(1) : selector,
		isSimple = simpleSelectorRE.test(nameOnly);
	// 是否是id选择器	
	if (element.getElementById && isSimple && maybeID) {
		found = element.getElementById(nameOnly);
		return found ? [found] : [];
	} else {
		// element不是元素类型,不是document,不是文档碎片
		if (element.nodeType !== 1 && element.nodeType !== 9 && element.nodeType !== 11) {
			return [];
		} else {
			if (isSimple && !maybeID && element.getElementsByClassName) {
				// 类选择器
				if (maybeClass) {
					// 返回是个数组
					return [].slice.call(element.getElementsByClassName(nameOnly));
				} else {
					//标签选择器
					return [].slice.call(element.getElementsByTagName(selector));
				}
			} else {
				// 其余
				return [].slice.call(element.querySelectorAll(selector));
			}
		}
	}
}
</script>

<div id="container">
	<p id="first">1111</p>
	<p class="target">2222</p>
	<p>3333</p>
	<p class="target">4444</p>
</div>

<script>
alert($('p').length);
alert($('#container').length);
alert($('div#container p:last-child').length);
</script>


本文完。
z
给个赞 2 人点赞
收藏 0 人收藏
评论 已有 0 条评论;以下用户言论只代表其个人观点,不代表 前端网(QDFuns) 的观点或立场。
登录 以后才能发表评论
最新评论
还没有任何评论呢,赶紧抢先来一发吧!
老姚 老姚 作者

与自己为敌,与自己为友,一边深挖思想,一边埋葬自己。

作者最新