zoukankan      html  css  js  c++  java
  • 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){
        var arrElements = (strTagName == "*" && oElm.all)? oElm.all :
            oElm.getElementsByTagName(strTagName);
        var arrReturnElements = new Array();
        strClassName = strClassName.replace(/\-/g, "\\-");
        var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
        var oElement;
        for(var i=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) {
        var children = (element || document).getElementsByTagName('*');
        var elements = new Array();
      
        for (var i = 0; i < children.length; i++) {
            var child = children[i];
            var classNames = child.className.split(' ');
            for (var j = 0; j < classNames.length; j++) {
                if (classNames[j] == className) {
                    elements.push(child);
                    break;
                }
            }
        }
      
        return elements;
    }

    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) {
        function acceptNode(node) {
            if (node.hasAttribute("class")) {
                var c = " " + node.className + " ";
                if (c.indexOf(" " + searchClass + " ") != -1)
                    return NodeFilter.FILTER_ACCEPT;
            }
            return NodeFilter.FILTER_SKIP;
        }
        var treeWalker = document.createTreeWalker(document.documentElement,
            NodeFilter.SHOW_ELEMENT, acceptNode, true);
        var returnElements = [];
        if (treeWalker) {
            var node = treeWalker.nextNode();
            while (node) {
                returnElements.push(node);
                node = treeWalker.nextNode();
            }
        }
        return returnElements;
    }

    XPath

    更加新式时髦的技术。

    下面取自Prototype.js框架。

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

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

    //查找一网页中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 || '*';
        var classes = searchClass.split(" "),
        patterns = "",
        xhtmlNamespace = "http://www.w3.org/1999/xhtml",
        namespaceResolver = (document.documentElement.namespaceURI === xhtmlNamespace)? xhtmlNamespace : null,
        returnElements = [],
        elements,
        _node;
        for(var j=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);
        return returnElements;
    }

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

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

    速度比较一览

    全平台兼顾速度的解决方案

    由Robert Nyman提供。

    var getElementsByClassName = function (className, tag, elm){
            if (document.getElementsByClassName) {
                    getElementsByClassName = function (className, tag, elm) {
                            elm = elm || document;
                            var elements = elm.getElementsByClassName(className),
                                    nodeName = (tag)? new RegExp("\\b" + tag + "\\b", "i") : null,
                                    returnElements = [],
                                    current;
                            for(var i=0, il=elements.length; i<il; i+=1){
                                    current = elements[i];
                                    if(!nodeName || nodeName.test(current.nodeName)) {
                                            returnElements.push(current);
                                    }
                            }
                            return returnElements;
                    };
            }
            else if (document.evaluate) {
                    getElementsByClassName = function (className, tag, elm) {
                            tag = tag || "*";
                            elm = elm || document;
                            var classes = className.split(" "),
                                    classesToCheck = "",
                                    xhtmlNamespace = "http://www.w3.org/1999/xhtml",
                                    namespaceResolver = (document.documentElement.namespaceURI === xhtmlNamespace)? xhtmlNamespace : null,
                                    returnElements = [],
                                    elements,
                                    node;
                            for(var j=0, jl=classes.length; j<jl; j+=1){
                                    classesToCheck += "[contains(concat(' ', @class, ' '), ' " + classes[j] + " ')]";
                            }
                            try     {
                                    elements = document.evaluate(".//" + tag + classesToCheck, elm, namespaceResolver, 0, null);
                            }
                            catch (e) {
                                    elements = document.evaluate(".//" + tag + classesToCheck, elm, null, 0, null);
                            }
                            while ((node = elements.iterateNext())) {
                                    returnElements.push(node);
                            }
                            return returnElements;
                    };
            }
            else {
                    getElementsByClassName = function (className, tag, elm) {
                            tag = tag || "*";
                            elm = elm || document;
                            var classes = className.split(" "),
                                    classesToCheck = [],
                                    elements = (tag === "*" && elm.all)? elm.all : elm.getElementsByTagName(tag),
                                    current,
                                    returnElements = [],
                                    match;
                            for(var k=0, kl=classes.length; k<kl; k+=1){
                                    classesToCheck.push(new RegExp("(^|\\s)" + classes[k] + "(\\s|$)"));
                            }
                            for(var l=0, ll=elements.length; l<ll; l+=1){
                                    current = elements[l];
                                    match = false;
                                    for(var m=0, ml=classesToCheck.length; m<ml; m+=1){
                                            match = classesToCheck[m].test(current.className);
                                            if (!match) {
                                                    break;
                                            }
                                    }
                                    if (match) {
                                            returnElements.push(current);
                                    }
                            }
                            return returnElements;
                    };
            }
            return getElementsByClassName(className, tag, elm);
    };

    综合以上方案,我得出了一个最理想的实现——兼容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){
        var nodes =  (node || document).getElementsByClassName(searchClass),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 = [],
          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)  result.push(current);
          }
          return result;
        }
      }
  • 相关阅读:
    C++11 lambda表达式(lambda expression)
    win 10 relog.exe 下载地址
    检测闩锁/自旋锁争用
    关于sql 锁和并发的一些记录
    FAST number_rows 意义解释
    网站实施SEO的步骤
    搜索引擎高级搜索指令浅析
    关于遇到高并发时候的一些总结
    Autofac 设置方法拦截器的两种方式
    C# MVC 进入Action 方法之后怎么使用MVC参数验证模型
  • 原文地址:https://www.cnblogs.com/zxktxj/p/2369095.html
Copyright © 2011-2022 走看看