document.getElementsByClassName的理想实现

来源:互联网 发布:淘宝卖家交保证金 编辑:程序博客网 时间:2024/04/30 01:28

各种实现方式

Pure DOM

通常先使用getElementsByTagName("*")取出文档中所有元素,然后进行遍历,使用正则表达式找出匹配的元素放入一个数组返回。由于IE5不支持document.getElementsByTagName("*"),要使用分支document.all以防错误。

The Ultimate getElementsByClassName方案,作者为Robert Nyman,05年实现,可见老外许多东西在很早以前就走得很远了。

//三个参数都是必需的,查找一网页中5007个类名为“cell”的元素,IE8历时1828 ~ 1844毫秒,
//IE6为4610 ~ 6109毫秒,FF3.5为46 ~ 48毫秒,opera10为31 ~ 32毫秒,Chrome为23~ 26毫秒,
//safari4为19 ~ 20毫秒
functiongetElementsByClassName(oElm, strTagName, strClassName){
    vararrElements = (strTagName == "*"&& oElm.all)? oElm.all :
        oElm.getElementsByTagName(strTagName);
    vararrReturnElements = newArray();
    strClassName = strClassName.replace(/\-/g,"\\-");
    varoRegExp = newRegExp("(^|\\s)"+ strClassName + "(\\s|$)");
    varoElement;
    for(vari=0; i < arrElements.length; i++){
        oElement = arrElements[i];
        if(oRegExp.test(oElement.className)){
            arrReturnElements.push(oElement);
        }
    }
    return(arrReturnElements)
}

另一个实现,由Dustin Diaz(《JavaScript Design Patterns》的作者)提供,但兼容性不如上面的,不支持IE5。

//后两参数是可靠的,查找一网页中5007个类名为“cell”的元素,IE8历时78毫秒,IE6历时125~171毫秒
//FF3.5为42 ~ 48毫秒,opera10为31 毫秒,Chrome为22~ 25毫秒,safari4为18 ~ 19毫秒
vargetElementsByClass = function(searchClass,node,tag) {
        varclassElements = newArray();
        if( node == null)
                node = document;
        if( tag == null)
                tag ='*';
        varels = node.getElementsByTagName(tag);
        varelsLen = els.length;
        varpattern = newRegExp("(^|\\s)"+searchClass+"(\\s|$)");
        for(i = 0, j = 0; i < elsLen; i++) {
                if( pattern.test(els[i].className) ) {
                        classElements[j] = els[i];
                        j++;
                }
        }
        returnclassElements;
}

还有个更古老级的,我从prototype.js1.01版本中找到的,它能支持多个类名的查找(上面两个不行)。它不支持IE5,效率一般般,但作为最早的框架之一,它已经做得很好,其他框架还没有想到这个呢!

//由于这是后期添加的,测试页面已被我删掉,没有做测试……
functiongetElementsByClassName(className, element) {
    varchildren = (element || document).getElementsByTagName('*');
    varelements = newArray();
 
    for(vari = 0; i < children.length; i++) {
        varchild = children[i];
        varclassNames = child.className.split(' ');
        for(varj = 0; j < classNames.length; j++) {
            if(classNames[j] == className) {
                elements.push(child);
                break;
            }
        }
    }
 
    returnelements;
}

DOM Tree Walker

使用document.createTreeWalker,这是个比较不常用的二级DOM方法。可惜IE全系列歇菜。

//查找一网页中5007个类名为“cell”的元素,FF3.5为104 ~ 119毫秒,opera10为230 ~ 265毫秒,
//Chrome为119 ~ 128毫秒,safari为28 ~ 32毫秒
vargetElementsByClassName = function(searchClass) {
    functionacceptNode(node) {
        if(node.hasAttribute("class")) {
            varc = " "+ node.className + " ";
            if(c.indexOf(" "+ searchClass + " ") != -1)
                returnNodeFilter.FILTER_ACCEPT;
        }
        returnNodeFilter.FILTER_SKIP;
    }
    vartreeWalker = document.createTreeWalker(document.documentElement,
        NodeFilter.SHOW_ELEMENT, acceptNode,true);
    varreturnElements = [];
    if(treeWalker) {
        varnode = treeWalker.nextNode();
        while(node) {
            returnElements.push(node);
            node = treeWalker.nextNode();
        }
    }
    returnreturnElements;
}

XPath

更加新式时髦的技术。

下面取自Prototype.js框架。

document.getElementsByClassName =function(className, parentElement) {
  if(Prototype.BrowserFeatures.XPath) {
    varq = ".//*[contains(concat(' ', @class, ' '), ' "+ className + " ')]";
    returndocument._getElementsByXPath(q, parentElement);
  }else {
    varchildren = ($(parentElement) || document.body).getElementsByTagName('*');
    varelements = [], child;
    for(vari = 0, length = children.length; i < length; i++) {
      child = children[i];
      if(Element.hasClassName(child, className))
        elements.push(Element.extend(child));
    }
    returnelements;
  }
};

由于这个是不能运行的,我们修改如下:

//查找一网页中5007个类名为“cell”的元素,FF3.5为33 ~ 48毫秒,opera10为31 ~ 32毫秒,
//Chrome为104 ~ 107毫秒,safari为18 ~ 21毫秒
vargetElementsByClassName = function(searchClass,node,tag) {
    node = node || document;
    tag = tag ||'*';
    varclasses = searchClass.split(" "),
    patterns ="",
    xhtmlNamespace ="http://www.w3.org/1999/xhtml",
    namespaceResolver = (document.documentElement.namespaceURI === xhtmlNamespace)? xhtmlNamespace :null,
    returnElements = [],
    elements,
    _node;
    for(varj=0, jl=classes.length; j<jl; j+=1){
        patterns +="[contains(concat(' ', @class, ' '), ' "+ classes[j] + " ')]";
    }
    try{
        elements = document.evaluate(".//"+ tag + patterns, node, namespaceResolver, 0, null);
    }
    catch(e) {
        elements = document.evaluate(".//"+ tag + patterns, node, null, 0,null);
    }
    while((_node = elements.iterateNext()))  returnElements.push(_node);
    returnreturnElements;
}

当然如果游览器原生支持,就用原生的。

各主流游览器的支持情况IE8IE7IE6FF3FF2Saf3Op9Op10ChromeNNNYNYYYY

速度比较一览

综合以上方案,我得出了一个最理想的实现——兼容IE5,让后面两个参数是可选的,能原生的原生,利用字面量与倒序循环提高效率……

//查找一网页中5007个类名为“cell”的元素,IE8历时1828 ~ 1844毫秒,
//IE6为125 ~ 172毫秒,IE8为93 ~ 94毫秒,FF3.5为0~1毫秒,opera10为0毫秒,Chrome为1毫秒,
//safari4为0毫秒
vargetElementsByClassName = function(searchClass,node,tag) {
    if(document.getElementsByClassName){
        return document.getElementsByClassName(searchClass)
    }else{   
        node = node || document;
        tag = tag ||'*';
        varreturnElements = []
        varels =  (tag === "*"&& node.all)? node.all : node.getElementsByTagName(tag);
        vari = els.length;
        searchClass = searchClass.replace(/\-/g,"\\-");
        varpattern = newRegExp("(^|\\s)"+searchClass+"(\\s|$)");
        while(--i >= 0){
            if(pattern.test(els[i].className) ) {
                returnElements.push(els[i]);
            }
        }
        returnreturnElements;
    }
}

用法:

varcollections = getElementsByClassName("red");

但它还是不如原生的getElementsByClassName,不能同时检索多个class

<h2class="red cell title">安装支持</h2>
 
<spanclass="cell red  ">jjj</span>
 
<divclass="filament_table  red cell">这是DIV</div>
 
#利用 var dd = getElementsByClassName("cell red") ,这三个元素都应该能被检索到!

因此,最最终的方案为:

vargetElementsByClassName = function(searchClass, node,tag) {
  if(document.getElementsByClassName){
    varnodes =  (node || document).getElementsByClassName(searchClass),result = [];
      for(vari=0 ;node = nodes[i++];){
        if(tag !=="*" && node.tagName === tag.toUpperCase()){
          result.push(node)
        }
      }
      returnresult
    }else{
      node = node || document;
      tag = tag ||"*";
      varclasses = searchClass.split(" "),
      elements = (tag ==="*" && node.all)? node.all : node.getElementsByTagName(tag),
      patterns = [],
      current,
      match;
      vari = classes.length;
      while(--i >= 0){
        patterns.push(newRegExp("(^|\\s)"+ classes[i] + "(\\s|$)"));
      }
      varj = elements.length;
      while(--j >= 0){
        current = elements[j];
        match =false;
        for(vark=0, kl=patterns.length; k<kl; k++){
          match = patterns[k].test(current.className);
          if(!match)  break;
        }
        if(match)  result.push(current);
      }
      returnresult;
    }
  }

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
form:
http://www.cnblogs.com/rubylouvre/archive/2009/07/24/1529640.html
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
我说下我没看这篇博文前对这东东实现的思路:
首先,是getElementsByClassName 那就说明返回值的类型 必然是一个数组,数组元素则是dom本身.
其次,className是可以复用的 并且同一个class里可以引用多个样式名 同时这些名均需要以空格隔开,因此需要利用正则/replace等等。
第三,需要遍历整个dom树,此时还要考虑到浏览器的兼容性、算法效率问题.
所以说,整体实现思路并不难,所以看到这几类的实现源码并不多。
--------------------------以上因某网友遇到类似问题只想着问从不思考而有感而发。