六狼论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

新浪微博账号登陆

只需一步,快速开始

搜索
查看: 167|回复: 0

YUI3 css 选择器实现分析

[复制链接]

升级  12%

172

主题

172

主题

172

主题

进士

Rank: 4

积分
560
 楼主| 发表于 2013-2-7 23:46:57 | 显示全部楼层 |阅读模式
当然,现代的库都判断了浏览器是否实现了 w3c selector-api,如果没有才采用下面的方法,目前只有ie系列会执行了。





以前分析过 extjs 的css 选择器实现,也粗略看过 jquery的css 选择器实现,这次又看了 yui3的选择器实现,发现每种实现都不同,而yui3或许不是效率最高的一个,却是实现起来最简单的一个。


Extjs 与 Jquery
 
extjs ,jquery都是属于自顶向下的分析方法,不同之处在于jquery将复杂选择符分解为简单单元后直接开始查找,去重,而extjs则分为编译与执行两个阶段,编译期生成对应选择器的dom操作函数,执行器直接执行生成函数即可:


div span


jquery 每次都分析选择器字符串,如分解为为["div","span"],然后再找到页面所有 div,再对这些div的所有子孙span进行合并去重得到最终结果。
extjs 则只有在第一次分析选择符字符串,生成了下列命令组成的函数:
 
alert(Ext.DomQuery.compile("div span"));//解析 selector 字串在编译期已经做了//递归也不需要,直接生成调用序列function (root) { var mode; ++batch; var n = root || document; //获取元素 n = getNodes(n, mode, "div"); mode = ""; n = getNodes(n,mode, "span"); //过滤元素 return nodup(n);} 以后每次相同的选择器字符串只需直接执行函数即可。


对于复杂的选择符,extjs,jquery都需要经过分析,选择,合并,过滤,去重的阶段。
 
 
 
YUI3 css selector :
 
而 yui3 选择了同以上两者都不同的策略,即自底向上的分析方法,降低了难度,对于


div span


首先分解出单元选择器 ["div","span"] (每个单元有复杂的数据结构),然后取出文档中的所有span元素,再对这些span元素进行过滤,条件为是否父(祖先)元素为div。只需执行过滤操作即可,没有必要执行合并与去重操作。


 
总体来说:yui3 包括分析与选择,过滤阶段。代码包括:dom-debug.js,selector-css3-debug.js
 
 
分析:
 
Y.Selector._tokenize 函数,将选择符分解为 combinator 分割的简单选择符,combinator 在css2下面包括
 
combinators: {            ' ': {                axis: 'parentNode'            },            '>': {                axis: 'parentNode',                direct: true            },            '+': {                axis: 'previousSibling',                direct: true            }        }, 并将它们对应到相应的dom节点的运动方位。


对应简单的选择符yui3其实就分为三类:




属性选择符,其中id,class被简化到这个类别
 
{                name: ATTRIBUTES,                re: /^\[(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,                fn: function(match, token) {                    var operator = match[2] || '',                        operators = Y.Selector.operators,                        test;                    // add prefiltering for ID and CLASS                    if ((match[1] === 'id' && operator === '=') ||                            (match[1] === 'className' &&                            document.documentElement.getElementsByClassName &&                            (operator === '~=' || operator === '='))) {                        token.prefilter = match[1];                        token[match[1]] = match[3];                    }                    // add tests                    if (operator in operators) {                        test = operators[operator];                        if (typeof test === 'string') {                            match[3] = match[3].replace(Y.Selector._reRegExpTokens, '\\$1');                            test = Y.DOM._getRegExp(test.replace('{val}', match[3]));                        }                        match[2] = test;                    }                    if (!token.last || token.prefilter !== match[1]) {                        return match.slice(1);                    }                }            } 标签选择符
 
{                name: TAG_NAME,                re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,                fn: function(match, token) {                    var tag = match[1].toUpperCase();                    token.tagName = tag;                    if (tag !== '*' && (!token.last || token.prefilter)) {                        return [TAG_NAME, '=', tag];                    }                    if (!token.prefilter) {                        token.prefilter = 'tagName';                    }                }            }  
 
伪类选择符
 
{                name: PSEUDOS,                re: /^:([\-\w]+)(?:\(['"]?(.+)['"]?\))*/i,                fn: function(match, token) {                    var test = Selector[PSEUDOS][match[1]];                    if (test) { // reorder match array                        return [match[2], test];                    } else { // selector token not supported (possibly missing CSS3 module)                        return false;                    }                }            } 其中的re即为分解选择符时用到的正则表达式,而fn则是附加到简单的选择符上面,用来过滤阶段的判断。




而 伪类和 combinator以及属性判断操作符都是动态从集合中取得:
 
//伪类对应过滤操作方式Selector[PSEUDOS]//连接符combinators: {     对应元素与对应dom方位操作}//属性判断操作符对应正则式operators[operator] 
则这就是yui3 css选择器部分的扩展机制,例如selector-css3-debug.js就是扩展了上述三者:


则最后给个复杂例子,如


#id1 div > a[href~='yiminghe'].ovcls


根据连接符则分为三个简单选择符:





每个结构的tests即为当前的过滤条件,而combinator即为通过当前过滤条件后的下一个将要检测的节点。注意direct属性,若direct为true,则表示失败后,结束,而direct为false,则表述还可以继续循着这个方向检测,如
" " direct为false,则父节点检测失败后,可以继续检测祖父节点。如 div span, 假设dom树中 span的父节点为 td,则如果td的父节点为div也是可以的。而 ">" direct就为true了,表示父节点不通过,这个节点就被过滤掉了。


选择过滤阶段:


分析出简单选择符后就简单了,Y.Selector._bruteQuery函数:取出最后一个简单选择符的对应文档元素 ,这步可以利用 id,tag,以及class(支持getElementsByClassName)。


剩下的就是单纯过滤了:
 
Y.Selector._filterNodes函数:

 
对选择阶段选出的所有元素,按简单选择符从后往前根据combinator定方位来变换检测节点,以及根据tests检测是否符合简单选择符,若一直到第1个简单选择符都符合,则当前节点保留,否则就过滤掉了。




 
 
 
 
 
 
您需要登录后才可以回帖 登录 | 立即注册 新浪微博账号登陆

本版积分规则

快速回复 返回顶部 返回列表