Javascript拼接HTML字符串的方法列舉及思路


轉載過來,去掉一些廢話吧。

目標:

方便的拼接字符串,不使用讓人眼暈的+=。使用過程如下:

1,先創建一個作為“模板”的字符串,如:’My name is ${name},I\’m ${age}.’

2,傳一個對象進去,其中包含了你要填進模板的值,如:{name:’LIX’,age:11}

3,最后你就能得到你想要的字符串了,如:My name is LIX,I’m 11.

調用方法:

mix('My name is "${name}",I\'m "${age}".',{name:'LIX',age:11})

mix('My name is "${0}",I\'m "${1}".',['LIX',11])

幾種實現:

1,replace+regexp

在司徒正美的文章里看到的。下面是簡化后的代碼:

1 function mix (str,group) {
2         str = str.replace(/\$\{([^{}]+)\}/gm,function (m,n) {
3             return (group[n] != undefined) ? group[n] : '';
4         })
5         return str;
6     }

其中function里面的m,n值得講一下。他們是從哪兒傳的值呢?就是正則表達式。string的replace方法,如果第2個參數是個函數的話,那函數的第1個參數值肯定就是“當前匹配到的字符串”。

但這里的函數有了兩個參數,第2個參數n,是什么?他就是你正則中的分組的第1組(被第1組()包起來的部分)——也就是說,如果你願意,還可以有很多組,然后replace的函數就可以有很多個參數了。

replace接受一個處理函數,其第一個參數是當前匹配到的子字符串,后面的參數就依次是正則匹配到的的第1組,第2組…

而函數中的return則是重中之重,如果沒有返回,那么替換就不會發生。replace正是用return回來的子串替換掉之前匹配到的子串的(就是參數m).

這個方法原理簡單易懂,代碼也少,但有個問題,我測試的時候發現這個比使用普通的+=串聯字符串慢了10倍不止!!太讓人心寒了啊

而我對這種方便又好用的拼字符串的方法非常眼熱,所以我只能考慮如何去提高其效率了。

方法二

既然replace+正則表達式效率不高,我就打算試試不用replace的方法。而查找字段標簽(即${name}這樣的)還是用正則來做,找到之后,我們把字符串在此標簽之前的部分,以及之后的部分都截取出來——恰好去掉${name}這一截,然后用+直接連上此標簽對應的值(例子里是LIX),如此循環。

代碼如下:

1 function loopMix0 (str,group) {
2         var reg = /\$\{([^{}]+)\}/gm, res;
3         while(res = reg.exec(str)) {
4             str = str.substr(0,res.index)+((group[res[1]] != undefined) ? group[res[1]] : '')+str.substr(res.index+res[0].length);
5         }
6         return str;
7     }

正則的exec方法是個比較奇特的方法,因為他不會一次把所有符合匹配條件的子串都返回,而是每次只返回當前匹配到的1個子串,詳細格式如此:

[當前匹配到的子串,(如果正則有分組,那么這里就是依次按分組匹配到的值,組1,組2...),index(這是當前匹配到的子串的index)]

如果要靠exec把所有能匹配的都給匹配了,那只有循環了。exec每次匹配后,都會改變他自己的lastIndex屬性,以便下次exec的時候不會又把以前匹配過的再匹配一次。當exec沒有返回結果的時候,就表示全部匹配完成了。

這樣就沒有用replace,而是用了字符串的原生方法,效率應該有提高吧?

現實是殘酷的,此方法和方法1的效率幾乎沒提高。這個方法的缺點很明顯,就是和replace一樣,每次循環中還是對整個字符串做操作(不停的賦予新值,然后用新值代入下次循環),效率當然不能提高。

方法三

明白了方法2的缺點,要做改進就很簡單了。我先新建一個空字符串,然后還是按上面的循環,只是每次都依次把字段標簽前的部分,字段標簽對應值,字段標簽后頭的部分,連接到這個空字符串上。這樣,雖然這個空字符串越來越長了,但我們再也沒有每次都對原始字符串進行修改了——原始字符串才是最長的好吧!!

代碼如下:

1 function loopMix1 (str,group) {
2         var reg = /\$\{([^{}]+)\}/gm, res,returnString = '',start = 0;
3         while(res = reg.exec(str)) {
4             returnString += str.substring(start,res.index)+((group[res[1]] != undefined) ? group[res[1]] : '');
5             start = res.index+res[0].length;
6         }
7         returnString += str.substr(start);
8         return returnString;
9     }

 其中有個變量start,保存着下一次str開始截取的起始位置,很重要。

PS:循環結束后還要在returnString上加上原始字符串的最后一截喲,不然你就得不到你“預期中的那么長”了。

這代碼有個變化就是不再是用的substr了,而是用的substring。因為substr的第2個參數是length,不再適合這里。

此方法比方法2快1倍有余!

說起substr和substring,就不得不提一個“萬人迷”(迷惑不清的迷):substr和substring的第2個參數各是什么意思?如何才能不混淆?

其實很簡單:substr比substring短得多,所以它迫切地需要“長度”,所以他的第2個參數是length.

方法四

方法3已經不錯了,但我是個精益求精的人。方法3在理論上還有個缺點,就是原始字符串str始終沒有改變,每次循環的時候都一樣長,會不會拖累正則以及substring的效率呢?

所以我就每次循環都把str變短了,反正前半截本來也是再也不要了的嘛。代碼如下:

 1 function loopMix2 (str,group) {
 2         var reg = /\$\{([^{}]+)\}/gm, res,returnString = '',start = 0;
 3         while(res = reg.exec(str)) {            
 4             returnString += str.substring(0,res.index)+((group[res[1]] != undefined) ? group[res[1]] : '');
 5             start = res.index+res[0].length;
 6             str = str.substr(start);
 7             reg.lastIndex = 0;
 8         }
 9         returnString += str;
10         return returnString;
11     }

 代碼中不只是把str變短了,還重置了reg的查詢下標,以防萬一。

這樣是不是比上個方法更進一步?答案是否定的,此方法比方法3慢,原因還是因為在循環里操作過多,導致效率不增反降。不過比方法1,2要快就是了。

方法五

由於我們的字段標簽${name}是比較容易識別的,在不故意把str弄錯的情況下,我們可以用string的原生方法:indexOf來將字段標簽提取出來,然后拼接。

思路是先找到’${‘,再按照得到的index,找到緊鄰的’}',然后取中間的值,也就得到了字段標簽的key值,然后從group中得到對應值,拼進結果字符串中。代碼如下:

 1 function loopMix3 (str,group) {
 2         var index=0,close=0,returnString = '',name = '';
 3         while((index = str.indexOf('${',index)) !== -1) {   
 4             returnString += str.substring(close,index);
 5             close = str.indexOf('}',index);
 6             name = str.substring(index+2,close);
 7             returnString += (group[name] != undefined) ? group[name] : ''
 8             index = close;
 9             close +=1;
10         }
11         returnString += str.substr(close);
12         return returnString;
13     }

 要點:其中要特別注意的是要隨時改變indexOf查找的起始位置(index),以及substring開始截取的位置(close)。

這個方法完全沒用正則,但效率還是沒有提高,完全比不上方法3,難道也是循環中操作太多?

PS:此方法的代碼有bug,比如字符串如下:’My name is “${name}”,this is a half ${name .{$name}’,這也就是我說的“故意”把字符串弄錯的情況,不過這個bug也是很好修復的,只要在找到一個${后,在查找}之前,再次繼續查找${,如果有結果,則continue下次循環。不過如此一來,又多了一個判斷,效率就更差了。

方法六

經常是寫着這段代碼,忽然就想起了另一種思路。比如此方法。

string有個自帶方法split,可以把字符串按某個分隔符拆分成數組,而且split支持正則表達式!也就是說我可以把我的原始字符串按${name}這樣的字段標簽折成數組!

然后呢,雖然把字符串折開了,但我們並沒有得到所有的字段標簽啊?string有個match方法,他能返回所有匹配參數的子串,而且他也接受正則,返回的也是個數組!

所以我現在拿這個正則做了兩個操作,一是將其作為分隔符把原字符串拆了,二是用它將原字符串里所有的字段標簽提取出來。

現在我們有了兩個數組,如果把這兩個數組從頭至尾拼合起來,恰好可以得到原始字符串!當然,我們肯定不能按原樣拼。。。

現在我們要循環數組並拼接了。在這之前先問大家兩個問題:

1.同一個字符串split與match同一個正則操作后,返回的數組哪個長?

2.'${name}${name}${name}${name}${name}${name}${name}${name}'.split('${name}')返回的數組是哪樣的?

問這兩個問題是很重要的,與此功能函數的實現密不可分。

很容易就能發現,match返回的數組永遠比split返回的數組length少1!所以呢,抱着循環盡量要短的宗旨,我們要對match返回的數組做循環而不是對split.

代碼如下:

function matchMix(str, group) {
      var reg = /\$\{[^{}]+\}/gm;
      var strArr = str.split(reg);
      var labelArr = str.match(reg);
      var returnString = '',
        i = 0,
        label, len = labelArr.length;
      for (; i < len; i++) {
        label = labelArr[i].slice(2, -1);
        returnString += strArr[i] + (group[label] != null ? group[label] : '');
      }
      return returnString + strArr[i];
    }

 PS:注意循環結束后還要為結果字符串加上split數組的最后一項啊!切記!

此方法比方法3要稍快一點,不過差距很小。我猜想在字符串比較長的情況下應該是此方法占優。

思路之外的優化

拿原始方法mix(replace+regexp)來說,他的效率還有沒有辦法提高呢?答案是有!

上面所有的思路,大家可以看到我用的是同一個regexp,即/\$\{([^{}]+)\}/gm,他是分了組的。而我們這里需要的匹配是很簡單的,其實可以不分組!因為我們只需要得到${name},就能很方便的得到name:用slice截斷一下就行了!

所以更改后的mix如下:

 1 //
 2 function mix (str,group) {
 3         return str.replace(/\$\{([^{}]+)\}/gm,function (m,n) {
 4             return (group[n] != void 1) ? group[n] : '';
 5         })
 6     }
 7 //slice版
 8     function mix1 (str,group) {
 9         return str.replace(/\$\{[^{}]+\}/gm,function (m,n) {
10             n = m.slice(2,-1);
11             return (group[n] != void 1) ? group[n] : '';
12         })
13     }

 單純用此兩者對比,效率孰高孰低?經測試,在所有瀏覽器下,mix1的效率都有略微提高

由此看來,正則表達式的效率實在是有待改進。

下面是一些相關測試:

mix與mix1對比

此改進方法同樣適合於其他思路。

總結

除了現成的方法1,后面的方法可以說都是我現想出來的,但是結果差強人意,並沒有如我所願效率越來越優的情況,只能說鍛煉了一下思路吧。

如果這個結果不算打擊,那我再告訴大家一個“振奮人心”的消息吧:IE9下,效率最高的是方法1,即原始replace+regexp的方法!后續所有方法都算白瞎了,哈哈!

不過IE9下最快的replace方法,也沒有chrome下最慢的replace方法執行的次數多。

說到這里,我要說一下:我是用jsperf.com測試的。測試地址

jsperf不但能對比,且每個測試都有執行次數,IE9下replace雖然效率最高,但執行次數還是趕不上chrome下replace的執行次數。

測試地址里面已經有6個版本,原因嘛是因為我測試着突然又想出了新思路,而jsperf里加新測試代碼就要新開版本。其中版本6是方法最全的。

經過反復在各瀏覽器里做測試,我發現:

1,chrome的效率是最快的,但測試結果非!常!不!穩!定!經常這次運行和下次運行完全是兩個結果
2,firefox的效率比chrome差些,但穩定,測試結果也與chrome結論一致
3,IE9效率最差!結論也很奇葩!

其他測試

3個效率最高的方法,方法一,方法三,方法六大比拼

原文:http://jo2.org/javascript-join-html-string/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM