再議《JavaScript代碼最佳化一例》

來源:互聯網
上載者:User

今天收到《程式員》第8期,讀到楊建華先生的“JavaSctipt代碼最佳化一例”。仔細讀來,覺得所做最佳化未能盡徹。所以專寫一篇文章來討論楊先生的例子。 原例可以在楊先生的blog上找到:
http://prowyh.spaces.live.com/blog/cns!EAAA8AB356F88EA0!403.entry

一、Regex的建立代碼
---
這樣的建立代碼實在冗餘:
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');

這裡看起來是用了字串串連運算,但由於變數聲明只運算一次,因此對效率沒什麼影響。而且可讀性強了,修改起來也方便。
讀注意這裡用到了Regex中的分組'( )',這在後面是會很有用的。

二、正則使用中的分組
---
代碼總是通過
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;
 }

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.