QWrap Selector解密之四:自选器转化
QWrap Selector解密之四:自选器转化
在《认识selector写法》中提到,我们把“没有关系符的选择器”叫“自选器”,例如“input#myId[value=hello]:enabled”,我们先看一下自选器里都有些什么内容,如下表:
在selector里,自选器经常被用来判断一某节点是否符合自选器,并且会被频繁调用,所以,我们需要高效的自选器判断函数,我们临时命名叫matcher吧。
以“[value=hello]”为例,来看一下matcher的两种方案。
方案1:解析input[value=hello],并立即判断
方案2:解析input[value=hello]成一个函数,以后每次matcher时就调用函数判断。
方案1的好处是即时解析即时用,不必缓存解析结果;方案2的好处是match多次时,只解析selector一次,将解析出的function多次运行。
自选器判断会经常用到,我们采用方案2的话,可以将这个解析功能独立出来,让代码清晰很多,并且也能保证效率,所以,QW.Selector提供了一个把自选器转化成过滤函数的方法:QW.Selector.selector2Filter。
这个方法除了在selector.js里面用到,在node.h.js里也会用到,例如,NodeH.ancestor(selector)方法里的selector参数就是一个自选器。
我们还是以自选器“input[value=hello]”为例,看一下它能转化成怎样的过滤函数。
我们有两种选择:
方案A:闭包:
方案B:拼字符串,再new Function:
可以看到,拼function方案得出的过滤函数,函数体的构成要简单得多。
事实上,QW.Selector在初期实现时,一直是闭包方式,后来到2010年时,转成了用字符串拼函数体的方式。具体实现参见内部函数s2f:
View Code
另外,在简版selector_sapmple.js中,还注释了一个更简版的只支持*、tagName、#id、.className四种选择器的自选器转换函数:
View Code
附:QWrap网址:http://www.qwrap.com
在《认识selector写法》中提到,我们把“没有关系符的选择器”叫“自选器”,例如“input#myId[value=hello]:enabled”,我们先看一下自选器里都有些什么内容,如下表:
格式 | 意义 |
* | 通配类型,特征:以*打头 |
E | 类型选择,特征:以tagName打头 |
#id | id选择,特征:“#”号 |
.className | className选择,特征:“.”号 |
[foo][foo="value"][foo~="value"] | 属性选择,特征:方括号。支持以下比较关系
[foo] isTrue|hasValue [foo="value"] equal [foo!="value"] unequal [foo~="value"] onePart [foo|="value"] firstPart [foo^="value"] beginWith [foo$="value"] endWith [foo*="value"] contains |
:enabled:nth-child(2n) | 伪类选择,特征:“:”号。例如以下伪类
first-child last-child only-child nth-child(nExpr) nth-last-child(nExpr) first-of-type last-of-type only-of-type nth-of-type(nExpr) nth-last-of-type(nExpr) empty parent not(selector) enabled disabled checked focus indeterminate |
在selector里,自选器经常被用来判断一某节点是否符合自选器,并且会被频繁调用,所以,我们需要高效的自选器判断函数,我们临时命名叫matcher吧。
以“[value=hello]”为例,来看一下matcher的两种方案。
方案1:解析input[value=hello],并立即判断
方案2:解析input[value=hello]成一个函数,以后每次matcher时就调用函数判断。
方案1的好处是即时解析即时用,不必缓存解析结果;方案2的好处是match多次时,只解析selector一次,将解析出的function多次运行。
自选器判断会经常用到,我们采用方案2的话,可以将这个解析功能独立出来,让代码清晰很多,并且也能保证效率,所以,QW.Selector提供了一个把自选器转化成过滤函数的方法:QW.Selector.selector2Filter。
/**
* 把一个selector字符串转化成一个过滤函数.
* @method selector2Filter
* @static
* @param {string} sSelector 过滤selector,这个selector里没有关系运算符(", >+~")
* @returns {function} : 返回过滤函数。
* @example:
var fun=selector2Filter("input.aaa");alert(fun);
*/
selector2Filter: function(sSelector) {
return s2f(sSelector);
},
我们还是以自选器“input[value=hello]”为例,看一下它能转化成怎样的过滤函数。
我们有两种选择:
方案A:闭包:
selector2Filter: function(sSelector) {
//解析selector,得到结果:
var tagName='INPUT';attrs=[['value','==','hello']];
return function(el){ //返回判断函数
if(el.tagName != tagName ) return false;
for(var i =0;i<attrs.length;i++){
if(el[attrs[i][0]] != attrs[i][2]) return false;//这里是理论简化极限
}
return true;
}
}
selector2Filter: function(sSelector) {
//解析selector,得到结果:
var s="return el.tagName=='INPUT' && el.value=='hello';";
return new Function('el',s);
}
可以看到,拼function方案得出的过滤函数,函数体的构成要简单得多。
事实上,QW.Selector在初期实现时,一直是闭包方式,后来到2010年时,转成了用字符串拼函数体的方式。具体实现参见内部函数s2f:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/*
* s2f(sSelector): 由一个selector得到一个过滤函数filter,这个selector里没有关系运算符(", >+~")
*/
var filterCache = {};
function s2f(sSelector, isForArray) {
if (!isForArray && filterCache[sSelector]) return filterCache[sSelector];
var pseudos = [],
//伪类数组,每一个元素都是数组,依次为:伪类名/伪类值
s = trim(sSelector),
reg = /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/g,
//属性选择表达式解析,thanks JQuery
sFun = [];
s = s.replace(/\:([\w\-]+)(\(([^)]+)\))?/g, //伪类
function(a, b, c, d, e) {
pseudos.push([b, d]);
return "";
}).replace(/^\*/g,
function(a) { //任意tagName缩略写法
sFun.push('el.nodeType==1');
return '';
}).replace(/^([\w\-]+)/g,//tagName缩略写法
function(a) {
sFun.push('el.tagName=="' + a.toUpperCase() + '"');
return '';
}).replace(/([\[(].*)|#([\w\-]+)|\.([\w\-]+)/g,//id缩略写法//className缩略写法
function(a, b, c, d) {
return b || c && '[id="' + c + '"]' || d && '[className~="' + d + '"]';
}).replace(reg, //普通写法[foo][foo=""][foo~=""]等
function(a, b, c, d, e) {
var attrGetter = Selector._attrGetters[b] || 'el.getAttribute("' + b + '")';
sFun.push(Selector._operators[c || ''].replace(/aa/g, attrGetter).replace(/vv/g, e || ''));
return '';
});
if (!(/^\s*$/).test(s)) {
throw "Unsupported Selector:\n" + sSelector + "\n-" + s;
}
for (var i = 0, pI; pI = pseudos[i]; i++) { //伪类过滤
if (!Selector._pseudos[pI[0]]) throw "Unsupported Selector:\n" + pI[0] + "\n" + s;
if (/^(nth-|not|contains)/.test(pI[0])) {
sFun.push('__SltPsds["' + pI[0] + '"](el,"' + encode4Js(pI[1]) + '")');
} else {
sFun.push('__SltPsds["' + pI[0] + '"](el)');
}
}
if (sFun.length) {
if (isForArray) {
return new Function('els', 'var els2=[];for(var i=0,el;el=els[i++];){if(' + sFun.join('&&') + ') els2.push(el);} return els2;');
} else {
return (filterCache[sSelector] = new Function('el', 'return ' + sFun.join('&&') + ';'));
}
} else {
if (isForArray) {
return function(els) {
return arrFilter(els, retTrue);
};
} else {
return (filterCache[sSelector] = retTrue);
}
}
}
另外,在简版selector_sapmple.js中,还注释了一个更简版的只支持*、tagName、#id、.className四种选择器的自选器转换函数:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/*
备用代码,更简版s2f
function s2f(sSelector){
var attrs=[];//属性数组,每一个元素都是数组,依次为:属性名/属性比较符/比较值
var s=sSelector;
var shorthands=[
[/\#([\w\-]+)/g,function(a,b){attrs.push('el.id=="'+b+'"');return '';}],//id过滤
[/^\*+/g,function(a,b){attrs.push('el.tagName');return '';}],//Element过滤
[/^([\w\-]+)/g,function(a,b){attrs.push('el.tagName=="'+b.toUpperCase()+'"');return '';}],//tagName过滤
[/\.([\w\-]+)/g,function(a,b){attrs.push('el.className && (" "+el.className+" ").indexOf(" '+b+' ")>-1');return '';}]//className过滤
];
for(var i=0,sh;sh=shorthands[i];i++){
s=s.replace(sh[0],sh[1]);
}
if(s) throw ("Unsupported Selector:\n"+sSelector+"\n"+s);
if(attrs.length){
return new Function('el','return '+attrs.join('&&'));
}
return function(el){return true;};
};
*/
附:QWrap网址:http://www.qwrap.com