首页 > 网站 > WEB开发 > 正文

第七章:选择器引擎

2024-04-27 14:07:39
字体:
来源:转载
供稿:网友

第七章:选择器引擎

jQuery凭借选择器风靡全球,各大框架类库都争先开发自己的选择,一时间内选择器变为框架的标配

早期的JQuery选择器和我们现在看到的远不一样。最初它使用混杂的xpath语法的selector。第二代转换为纯CSS的自定义伪类,(比如从xpath借鉴过来的位置伪类)的sizzle,但sizzle也一直在变,因为他的选择器一直存在问题,一直到JQuery1.9才搞定,并最终全面支持css3的结构伪类。

2005 年,Ben Nolan的Behaviours.js 内置了闻名于世的getElementBySelector,是第一个集成事件处理,css风格的选择器引擎与onload处理的类库,此外,日后的霸主PRototype.js页再2005年诞生。但它勉强称的上是,选择器$与getElementByClassName在1.2出现,事件处理在1.3,因此,Behaviour.js还风光一时。

本章从头至尾实验制造一个选择器引擎。再次,我们先看看前人的努力:

1.浏览器内置寻找元素的方法

请不要追问05年之前开发人员是怎么在这种缺东缺西的环境下干活的。那时浏览器大战正酣。程序员发明navugator.userAgent检测进行"自保"!网景战败,因此有关它的记录不多。但IE确实留下不少资料,比如取得元素,我们直接可以根据id取得元素自身(现在所有浏览器都支持这个特性),不通过任何API ,自动映射全局变量,在不关注全局污染时,这是个很酷的特性。又如。取得所有元素,使用document.All,取得某一种元素的,只需做下分类,如p标签,document.all.tags("p")。

有资料可查的是 getElementById , getElementByTagName是ie5引入的。那是1999年的事情,伴随一个辉煌的产品,window98,捆绑在一起,因此,那时候ie都倾向于为IE做兼容。

(感兴趣的话参见让ie4支持getElementById的代码,此外,还有getElementByTagsName的实现)

但人们很快发现问并无法选取题了,就是IE的getElementById是不区分表单元素的ID和name,如果一个表单元素只定义name并与我们的目标元素同名,且我们的目标元素在它的后面,那么就会选错元素,这个问题一直延续到ie7.

IE下的getElementsByTagesName也有问题。当参数为*号通配符时,它会混入注释节点,并无法选取Object下的元素。

(解决办法略去)

此外,w3c还提供了一个getElementByName的方法,这个IE也有问题,它只能选取表单元素。

在Prototype.js还未到来之前,所有可用的只有原生选择器。因此,simon willson高出getElementBySelector,让世人眼前一亮。

之后的过程就是N个版本的getElementBySlelector,不过大多数是在simon的基础上改进的,甚至还讨论将它标准化!

getElementBySlelector代表的是历史的前进。JQuery在此时优点偏向了,prototype.js则在Ajax热浪中扶摇直上。不过,JQuery还是胜利了,sizzle的设计很特别,各种优化别出心裁。

Netscape借助Firefox还魂,在html引入xml的xpath,其API为document.evaluate.加之很多的版本及语法复杂,因此没有普及开来。

微软为保住ie占有率,在ie8上加入querySelector与querySlectorAll,相当于getElementBySelector的升级版,它还支持前所未有的伪类,状态伪类。语言伪类和取反伪类。此时,Chrome参战,激发浏览器标准的热情和升级,ie8加入的选择器大家都支持了,还支持的更加标准。此时,还出现了一种类似选择器的匹配器————matchSelector,它对我们编写选择器引擎特别有帮助,由于是版本号竞赛时诞生的,谁也不能保证自己被w3c采纳,都带有私有前缀。现在css方面的Selector4正在起草中,querySeletorAll也只支持到selector3部分,但其间兼容性问题已经很杂乱了。

2.getElementsBySelector

让我们先看一下最古老的选择器引擎。它规定了许多选择器发展的方向。在解读中能涉及到很多概念,但不要紧,后面有更详细的解释。现在只是初步了解下大概蓝图。

/* document.getElementsBySelector(selector)    version 0.4 simon willson march 25th 2003    -- work in phonix0.5 mozilla1.3 Opera7 ie6     */    function getAllchildren(e){        //取得一个元素的子孙,并兼容ie5        return e.all ? e.all : e.getElementsByTgaName('*');    }    document.getElementsBySelector = function(selector){        //如果不支持getElementsByTagName 则直接返回空数组        if (!document.getElementsByTgaName) {            return new Array();        }        //切割CSS选择符,分解一个个单元格(每个单元可能代表一个或多个选择器,比如p.aaa则由标签选择器和类选择器组成)        var tokens = selector.split(' ');        var currentContext = new Array(document);        //从左至右检测每个单元,换言此引擎是自顶向下选择元素        //如果集合中间为空,立即中至此循环        for (var i = 0 ; i < tokens.length; i++) {            //去掉两边的空白(并不是所有的空白都没有用,两个选择器组之间的空白代表着后代迭代器,这要看作者们的各显神通)            token = tokens[i].replace(/^/s+/,'').replace(//s+$/,'');            //如果包含ID选择器,这里略显粗糙,因为它可能在引号里边。此选择器支持到属性选择器,则代表着可能是属性值的一部分。            if (token.indexOf('#') > -1) {                //假设这个选择器是以tag#id或#id的形式,可能导致bug(但这些暂且不谈,沿着作者的思路看下去)                var bits =token.split('#');                var tagName = bits[0];                var id = bits[1];                //先用id值取得元素,然后判定元素的tagName是否等于上面的tagName                //此处有一个不严谨的地方,element可能为null,会引发异常                var element = document.getElementById(id);                if(tagName && element.nodeName.toLowerCase() != tagName) {                    //没有直接返回空结合集                    return new Array();                }                //置换currentContext,跳至下一个选择器组                currentContext = new Array(element);                continue;            }            //如果包含类选择器,这里也假设它以.class或tag.class的形式            if (token.indexOf('.') > -1){                var bits = token.split('.');                var tagName = bits[0];                var className = bits[1];                if (!tagName){                    tagName = '*';                }                //从多个父节点,取得它们的所有子孙                //这里的父节点即包含在currentContext的元素节点或文档对象                var found = new Array;//这里是过滤集合,通过检测它们的className决定去留                var foundCount = 0;                for (var h = 0; h < currentContext.length; h++){                    var elements;                    if(tagName == '*'){                        elements = getAllchildren(currentContext[h]);                    } else {                        elements = currentContext[h].getElementsByTgaName(tagName);                    }                    for (var j = 0; j < elements.length; j++) {                        found[foundCount++] = elements[j];                    }                }                currentContext = new Array;                for (var k = 0; k < found.length; k++) {                    //found[k].className可能为空,因此不失为一种优化手段,但new regExp放在//外围更适合                    if (found[k].className && found[k].className.match(new RegExp('//b'+className+'//b'))){                        currentContext[currentContextIndex++] = found[k];                    }                }                continue;            }            //如果是以tag[attr(~|^$*)=val]或[attr(~|^$*)=val]的组合形式            if (token.match(/^(/w*)/[(/w+)([=~/|/^/$/*]?)=?"?([^/]"]*)"?/]$/)){                var tagName = RegExp.$1;                var attrName = RegExp.$2;                var attrOperator = RegExp.$3;                var attrValue = RegExp.$4;                if (!tagName){                    tagName = '*';                }                //这里的逻辑以上面的class部分相似,其实应该抽取成一个独立的函数                var found = new Array;                var foundCount = 0;                for (var h = 0; h < currentContext.length; h++){                    var elements;                    if (tagName == '*') {                        elements = getAllchildren(currentContext[h]);                    } else {                        elements = currentContext[h].getElementsByTagName(tagName);                    }                    for (var j = 0; j < elements.length; j++) {                        found[foundCount++] = elements[j];                    }                }                currentContext = new Array;                var currentContextIndex = 0;                var checkFunction;                //根据第二个操作符生成检测函数,后面的章节有详细介绍 ,请继续关注哈                switch (attrOperator) {                    case '=' : //                    checkFunction = function(e){ return (e.getAttribute(attrName) == attrValue);};                    break;                    case '~' :                    checkFunction = function(e){return (e.getAttribute(attrName).match(new RegExp('//b' +attrValue+ '//b')));};                    break;                    case '|' :                    checkFunction = function(e){ return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?')));};                    break;                    case '^' :                     checkFunction = function(e) {return (e.getAttribute(attrName).indexOf(attrValue) == 0);};                    break;                    case '$':                    checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length);};                    break;                    case '*':                    checkFunction  = function(e) {return (e.getAttribute(attrName).indexOf(attrValue) > -1 );}                    break;                    default :                    checkFunction = function(e) {return e.getAttribute(attrName);};                 }                currentContext = new Array;                var currentContextIndex = 0 ;                for (var k = 0; k < found.length; k++) {                    if (checkFunction(found[k])) {                        currentContext[currentContextIndex++] = found[k];                    }                }                continue;            }            //如果没有 # . [ 这样的特殊字符,我们就当是tagName            var tagName = token;            var found = new Array;            var foundCount = 0;            for (var h = 0; h < currentContext.length; h++) {                var elements = currentContext[h].getElementsByTgaName(tagName);                for (
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表