返回列表

document.getElementsByClassName的理想实现

默认分类 2011-04-28 17:14:00

转载:http://www.cnblogs.com/rubylouvre/archive/2009/07/24/1529640.html?login=1#commentform

document.getElementsByClassName的理想实现

各种实现方式

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毫秒
function getElementsByClassName(oElm, strTagName, strClassName){
    vararrElements = (strTagName =="*" && oElm.all)? oElm.all :
        oElm.getElementsByTagName(strTagName);
    vararrReturnElements =new Array();
    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毫秒
var getElementsByClass = function(searchClass,node,tag) {
        var classElements = new Array();
        if ( node ==null )
                node = document;
        if ( tag ==null )
                tag = '*';
        var els = node.getElementsByTagName(tag);
        var elsLen = els.length;
        var pattern =new RegExp("(^|\\s)"+searchClass+"(\\s|$)");
        for (i = 0, j = 0; i < elsLen; i++) {
                if ( pattern.test(els[i].className) ) {
                        classElements[j] = els[i];
                        j++;
                }
        }
        return classElements;
}

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

//由于这是后期添加的,测试页面已被我删掉,没有做测试……
function getElementsByClassName(className, element) {
    varchildren = (element || document).getElementsByTagName('*');
    varelements = newArray();
    for(var i = 0; i < children.length; i++) {
        var child = children[i];
        var classNames = 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毫秒
var getElementsByClassName = function(searchClass) {
    functionacceptNode(node) {
        if (node.hasAttribute("class")) {
            var c =" " + node.className + " ";
            if (c.indexOf(" "+ searchClass + " ") != -1)
                return NodeFilter.FILTER_ACCEPT;
        }
        return NodeFilter.FILTER_SKIP;
    }
    vartreeWalker = document.createTreeWalker(document.documentElement,
        NodeFilter.SHOW_ELEMENT, acceptNode,true);
    varreturnElements = [];
    if(treeWalker) {
        var node = 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(var i = 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毫秒
var getElementsByClassName = 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;
}

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

各主流游览器的支持情况
IE8IE7IE6FF3FF2Saf3Op9Op10Chrome
NNNYNYYYY

速度比较一览

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

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

用法:

var collections = getElementsByClassName("red");

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

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

因此,最最终的方案为:

var getElementsByClassName = 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)
        }else{
          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(new RegExp("(^|\\s)" + classes[i] + "(\\s|$)"));
      }
      varj = elements.length;
      while(--j >= 0){
        current = elements[j];
        match = false;
        for(var k=0, kl=patterns.length; k<kl; k++){
          match = patterns[k].test(current.className);
          if (!match)  break;
        }
        if (match)  result.push(current);
      }
      returnresult;
    }
  }
标签:javascript
4
0
(请您对文章做出评价)

posted on 2009-07-24 03:07司徒正美 阅读(4101)评论(5)编辑收藏

评论

#1楼 2009-07-31 18:21Selfocus    

顶一下。不知LZ研究过jQuery获取元素的方法没有,甚是强大!  回复 引用 查看  

#2楼 2009-09-04 21:01兲恏蒿    

不错,就是有点麻烦  回复 引用 查看  

#3楼 2010-10-09 01:13笨蛋的座右铭    

你这个函数是错误的,
if(document.getElementsByClassName){

return document.getElementsByClassName(searchClass)

}
问:标准里没有fatherDom.getElementsByClassName("childTag",classNames)
 回复 引用 查看  

#4楼[楼主] 2010-10-09 19:45司徒正美    

@笨蛋的座右铭
没有

getElementsByClassName只有一个参数
不过调用者可以是document或元素节点  回复 引用 查看  

#5楼 2010-10-12 15:54笨蛋的座右铭    

你这个函数确实是不正确的,看如下代码在IE和opera下运行结果:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>getElementByClass</title>
</head>
<body>
    <script type="text/javascript">
     var getElementsByClassName = function (searchClass, node,tag) {
        if(document.getElementsByClassName){
            return  document.getElementsByClassName(searchClass)
        }else{
            node = node || document;
            tag = tag || "*";
            var classes = searchClass.split(" "),
            elements = (tag === "*"&& node.all)? node.all : node.getElementsByTagName(tag),
            patterns = [],
            returnElements = [],
            current,
            match;
            var i = classes.length;
            while(--i >= 0){
                patterns.push(new RegExp("(^|\\s)"+ classes[i] + "(\\s|$)"));
            }
            var j = elements.length;
            while(--j >= 0){
                current = elements[j];
                match = false;
                for(var k=0, kl=patterns.length; k<kl; k++){
                    match = patterns[k].test(current.className);
                    if (!match)  break;
                }
                if (match)  returnElements.push(current); 
            }
            return returnElements;
        }
    }
    window.onload = function(){
        var father = document.getElementById("father");
        alert(getElementsByClassName("aaa bbb",father,"span").length);
    }
    </script>
    <div id="father">
        <span class="aaa bbb ccc"><span>
        <span class="aaa bbb zzz"></span>
    </div>
        <span class="aaa bbb ccc"><span>
        <span class="aaa bbb zzz"></span>
</body>
</html>

//ie 2
//opera 4
 回复 引用 查看  

haiyang5210:
//扩展getElementsByClassName
function getElementsByClassName(searchClass, node,tag) {
        if(document.getElementsByClassName){
          var nodes = (node || document).getElementsByClassName(searchClass),result = nodes;
          if(tag!=undefined){
              result = [];
             for(var i=0 ;node = nodes[i++];){
                 if(tag !== "*" && node.tagName === tag.toUpperCase()){
                      result.push(node);
                 }else{
                      result.push(node);
                 }
             }
          }
          return result;
        }else{
            node = node || document;
            tag = tag || "*";
            var classes = searchClass.split(" "),
            elements = (tag === "*" && node.all)? node.all : node.getElementsByTagName(tag),
            patterns = [],
            returnElements = [],
            current,
            match;
            var i = classes.length;
            while(--i >= 0){
                patterns.push(new RegExp("(^|\\s)" + classes[i] + "(\\s|$)"));
            }
            var j = elements.length;
            while(--j >= 0){
                current = elements[j];
                match = false;
                for(var k=0, kl=patterns.length; k<kl; k++){
                    match = patterns[k].test(current.className);
                    if (!match)  break;
                }
                if (match)  returnElements.push(current); 
            }
            return returnElements;
        }
    };