• 再议《JavaScript代码优化一例》


    今天收到《程序员》第8期,读到杨建华先生的“JavaSctipt代码优化一例”。仔细读来,觉得所做优化未能尽彻。所以专写一篇文章来讨论杨先生的例子。 原例可以在杨先生的blog上找到:
    http://prowyh.spaces.live.com/blog/cns!EAAA8AB356F88EA0!403.entry

    一、正则表达式的创建代码
    ---
    这样的创建代码实在冗余:
    var fnRE = /functor_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}/i;
    var objRE = /object_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}$/i;
    var objRE_r =  /radio_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}_r/i;
    var objRE_a = /object_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}_a/i;
    var objRE_m =  /radio_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}_m/i;
    var objRE_d =  /radio_[0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12}_d/i;

    仔细读来,其实就是一个添加前后缀的GUID。那么可否写成如下:
    var GUID = '([0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12})';
    var fnRE =     new RegExp('(functor_)'+ GUID, 'i');
    var objRE =    new RegExp('(object_)' + GUID + '$', 'i');
    var objRE_r =  new RegExp('(radio_)'  + GUID + '_(r)', 'i');
    var objRE_a =  new RegExp('(object_)' + GUID + '_(a)', 'i');
    var objRE_m =  new RegExp('(radio_)'  + GUID + '_(m)', 'i');
    var objRE_d =  new RegExp('(radio_)'  + GUID + '_(d)', 'i');


    这里看起来是用了字符串连接运算,但由于变量声明只运算一次,因此对效率没什么影响。而且可读性强了,修改起来也方便。
    读注意这里用到了正则表达式中的分组'( )',这在后面是会很有用的。

    二、正则使用中的分组
    ---
    代码总是通过
    aryAList[_match[0].split("_")[1]] = "a_";
    这样的形式来从匹配中分离GUID,但如果使用上面的分组,那么这项运算就不必要了。简单的使用
    aryAList[_match[2]] = "a_";
    就可以得到结果。

    三、应注意DOM引用的耗时
    ---
    代码中,在循环内不断地访问DOM对象的成员,然而DOM对象的成员存取是耗时的。更细的说,每一个成员
    都会通过COM组件封装,因此效率是差的。所以下面的代码:
     else if ((_match = _obj.name.match(objRE_m)) != null)  {
     }
     else if ((_match = _obj.name.match(objRE_d)) != null)  {
     }

    应当被改作:
    var name = _obj.name;
     else if ((_match = name.match(objRE_m)) != null)  {
     }
     else if ((_match = name.match(objRE_d)) != null)  {
     }

    四、过于复杂的逻辑
    ---
    代码过于依赖其它语言的编程经验,而忽略了JavaScript的自身特性。因此实现的逻辑中规中矩,但是难以
    扩展,而且复杂。例如循环中的大量if..else if ...连用。后文单独给出这部分的优化。

    五、从StringBuilder()接口来看,优化程度不够
    ---
    文章提到StringBuilder是一个字符串构建的高效对象。我留意到它的使用是:
    objectListEx.append(_id + ":" + _r + ":" + _a + ":" + _m + ":" + _d + ";");
    那么可以说这个对象的优化是不够的。因为这里传入一个字符串参数,而传入参数又用字符串连接运算,
    效率提升很有限。
    如果StringBuilder是用array.join来实现字符串拼接的话,那么更加良好的方式是允许在append中使用多
    个参数。例如:
    objectListEx.append = function() {
      this.push.apply(this, arguments);
    }
    objectListEx.toString = function() {
      return this.join('');
    }

    那么,上例的添加就可以写成:
    objectListEx.append(_id , ":" , _r , ":" , _a , ":" , _m , ":" , _d , ";");
    这就避免了多余的字符串连接运算。

    六、新的优化后版本
    ---
    // optimized version
    var functorListEx = new StringBuilder();
    var objectListEx = new StringBuilder();

    var coll = document.getElementsByTagName("INPUT");
     
    // regular expression for matching
    var GUID = '([0-9A-Za-z]{8}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{4}-[0-9A-Za-z]{12})';
    var fnRE =     new RegExp('(functor_)'+ GUID, 'i');
    var objRE =    new RegExp('(object_)' + GUID + '$', 'i');
    var objRE_r =  new RegExp('(radio_)'  + GUID + '_(r)', 'i');
    var objRE_a =  new RegExp('(object_)' + GUID + '_(a)', 'i');
    var objRE_m =  new RegExp('(radio_)'  + GUID + '_(m)', 'i');
    var objRE_d =  new RegExp('(radio_)'  + GUID + '_(d)', 'i');

    // helper data structures used by optimized algorithm
    var aryObjList = new Array();
    var aryRList = new Array();
    var aryAList = new Array();
    var aryMList = new Array();
    var aryDList = new Array();

    var aryList = {
      r: aryRList,
      a: aryAList,
      m: aryMList,
      d: aryDList
    }

    // one pass scan to traverse the nodes collection (coll) to build functorListEx
    // and intermediate arrays
    for (var i=0,imax=coll.length; i<imax; i++) {
     var _obj = coll[i];
     if (!_obj.checked) continue; // <-- 更快的检测
     if (_obj.type != "checkbox" && _obj.type != "radio") continue;

     var id = _obj.id, name = _obj.name;
     var _match = id.match(fnRE) || name.match(objRE_r) || id.match(objRE_a) ||
       name.match(objRE_m) || name.match(objRE_d) || id.match(objRE);

     if (!_match) continue;

     var tag = _match[3], tag2 = tag+'_', guid = _match[2];
     switch (tag) {
       'a': aryList[tag][guid] = tag2; break;

       'r', 'm', 'd':
         aryList[tag][guid] = tag2 + _obj.value; break;

       default :
         if (_match[1]=='functor_') {
           functorListEx.append(guid, ";")
         }
         else { // for _match[1]=='object_'
           aryObjList.push(guid)
         }
     }
    }

    // further process to build objectListEx from the intermediate arrays
    for (var i=0, imax=aryObjList.length; i<imax; i++) {
     var id = aryObjList[i];
     var r = aryRList[id] || "";
     var a = aryAList[id] || "";
     var m = aryMList[id] || "";
     var d = aryDList[id] || "";

     objectListEx.append(id , ":" , r , ":" , a , ":" , m , ":" , d , ";");
    }

    七、又一处小的优化
    ---
    刚才想了想,其实上面代码中的switch还是啰嗦了。下面做一下下小的优化:
     switch (_match[1] + tag) {
       'functor_undefined': functorListEx.append(guid, ";"); break;

       'object_undefined': aryObjList.push(guid); break;

       'object_a': aryList[tag][guid] =  tag2 ; break;

       default:  // for r,m,d
         aryList[tag][guid] = tag2 + _obj.value;
     }

  • 相关阅读:
    常用脚本语言Perl,Python,Ruby,Javascript一 Perl,Python,Ruby,Javascript
    Android中Linux suspend/resume流程
    Linux计算机进程地址空间与内核装载ELF
    Linux操作系统工作的基础
    Tomcat 系统架构与设计模式,第 2 部分: 设计模式分析
    基础算法题
    多模式字符串匹配(转)
    二叉树题集
    概率及数学题
    字符串算法题
  • 原文地址:https://www.cnblogs.com/encounter/p/2188663.html
Copyright © 2020-2023  润新知