字符串和數組在程序編寫過程中是十分常用的類型,因此程序語言都會將String和Array作為基本類型,並提供許多字符串和數組的方法來簡化對字符串的操作。JavaScript里面也提供了String類型和Array類型,並且有很多基本的String方法和Array方法來方便地對字符串進行合並、查找、替換、截取等處理。
JavaScript作為一個腳本語言,又提供了一種動態解析運行的機制,而這特性,又讓使得在String
操作的時候出現一些結合使用Array
的有趣方法。這些方法可能有些偏門有點奇怪,但有時在效率、可讀性、復用性上表現得卻更好。
重復字符串
常常我們想要把字符串多次打印出來(比如想要個分割線),我們就需要將一個字符串重復多次, 可惜JavaScript並沒有提供類似repeat
這樣的方法。當然我們可以用循環來拼接出來,但是我們可以利用JavaScript中Array的join方法來實現repeat
function repeat(str, n) { var arr = new Array(n+1); return arr.join(str); } //output: //-------
利用n+1個Array元素產生的n個間隙,再以目標字符串來拼接,我們就能得到字符串重復的功能。
擴展String的prototype使方法應用於所有字符串
JavaScript的對象繼承和方法尋找是基於原型鏈(prototype chain),所有使用着的字符串都可以說是繼承於String的對象,我們可以為String對象的prototype
添加方法或者屬性,這樣該方法就可以應用到所有我們使用的對象上了。比如上邊的repeat
方法,就可以改成:
String.prototype.repeat = function(n) { var arr = new Array(n+1); return arr.join(this); }; document.write('-'.repeat(21)); //output: //---------------------
然后,直接通過字符串調用repeat
方法,就可以得到跟上邊一樣的結果。
這可以讓我們實現對字符串方法的擴充,簡潔對字符串的操作,但是這會“污染”了JavaScript的String,當代碼被轉到其他文件但是那個文件下並沒有得到這段擴充,就可能會造成找不到該方法;另外,調用prototype
擴展方法比直接調用方法要稍微“慢”一些,因為JavaScript會先去在字符串對象自身的方法中嘗試尋找,再找到String的prototype的方法;再者也許在將來我們擴充的方法(比如repeat)變成了標准方法了,再使用這代碼就會覆蓋了標准方法,得到不一致的結果。
但是忽略這些考慮,擴充JavaScript標准類型的prototype還是會給編程帶來許多的遍歷。
用Array作StringBuilder
在很多高級語言中,加號(+)在字符串的操作中被賦予了更多的意義:作為字符串拼接的操作符。不過在Java和C#中,我們也知道如何頻繁進行字符串拼接的操作,使用加號(+)就會產生效率問題,因此在這種情況下就會推薦使用StringBuilder。
JavaScript也支持使用加號(+)來進行字符串拼接,那么也會有存在效率問題呢。可是JavaScript並沒有提供StringBuilder這樣的類。
其實在Java,C#中使用StringBuilder時,我們多數也是用append方法,而很少會用insert。好在JavaScript的Array是不限大小自動增長的,所以我們就可以利用Array來做StringBuilder,最后再join空字符串來拼接出目標字符串。
var sb = []; for(var i = 0; i <=21; i++) { sb.push(i); } document.write(sb.join('')); //output: //0123456789101112131415161718192021
到底是用Array做StringBuilder還是直接字符串拼接,jsPerf上有過很多testcases比較兩者的效率,但是因為初始值、環境、字符串長度等原因,所以結果不一。其實字符串內容不是很大,或者可以使用多個加號(+)組合在一起,那么字符串拼接還是可以的;若是在代碼不同地方對同一字符串變量進行追加,那么可能使用Array配合join
會更好。
用split替代字符串的子串查找和替換
在字符串的操作中,很常出現的就是想要從字符串中查找一個子字符串是否存在,然后截取出該字符串,抑或是將該子字符串替換成其它字符串。
比如給一個文件名,希望根據點(.)分割獲取基本名和后綴名。先來看看使用標准String方法實現的這些操作:
function getBaseName(str) { var pos = str.lastIndexOf('.'); if(pos < 0)return str; return str.substring(0, pos); } function getExtension(str) { var pos = str.lastIndexOf('.'); if(pos < 0)return ''; return str.substr(pos+1); } var fileName = 'hello_world.js'; document.write(getBaseName(fileName)); document.write('<br />'); document.write(getExtension(fileName)); //output: //hello_world //js
(除了substr
和substring
外,JavaScript還有slice
都可以用來獲取字符串的子串,但也正是因為選擇太多,常常讓我在出現選擇恐慌,還有位置是該不該+1,對負數是如何處理也讓我揪心。)
之前看到可以通過join
把數組變成字符串,也可以利用String的split
的方法把字符串變成數組。對於上邊取文件名及擴展名的問題,我們就可以根據“.”把文件名分裂成數組各個部分,那么假如得到的數字大於1(后綴名存在),則所得數字的最后一個元素就是文件的擴展名了:
function getBaseName(str) { var segs = str.split('.'); if(segs.length > 1) segs.pop(); return segs.join('.'); } function getExtension(str) { var segs = str.split('.'); if(segs.length <= 1)return ''; return segs.pop(); }
考慮到文件名中可能包含多個“.”,所以我們還是需要用“.”把除了最后一部分外的各個部分join回來。
看到可以對字符串先split
再join
,就可以想到,我們可以想到對於這兩個方法的參數可以傳入不同的字符串,這樣就起到了代替String的replace
方法進行子串替換的功能了,而且還是全局替換。
比如希望把所有的下划線(_)替換成橫杠(-):
var str = 'hello_from_ider_to_world'.split('_').join('-'); document.write(str); //Output: // hello-from-ider-to-world
相對於String的replace
方法,該方法的有點在於:可以實現全局替換;而若要讓replace
能夠全局替換,則需要傳入正則表達式對象(RegExp)而不能是字符串作為第一參數。
replace可接受RegExp、Function作為參數
很多人知道String的replace方法是用來替換字符串子串的,也可能知道它可以接受正則表達式作為第一參數,而且如何要替換所有出現的地方,就必須要用RegExp並包含global標記。
比如之前的替換操作,用replace就應該是:
var str = 'hello_from_ider_to_world'.replace(/_/g, '-'); document.write(str);
再比如很常用的trim方法,雖然JavaScript並沒有提供我們也可以自己很快的實現:
String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); };
我們知道正則表達式一個很強大的功能就是向后引用(Back Reference),實際上JavaScript的replace
不僅在第一個參數內做向后引用,而且在替換字符串上,也可以進行向后引用,只是很多地方可能用反斜杠(\)加數字作為標示而JavaScript則是用美刀($)加數字作為標示。
var friends = 'friends of Ider, friend of Angie'; var result = friends.replace(/(friends?) of (\w+)/g, "$2's $1"); document.write(result); //output: //Ider's friends, Angie's friend
通過在替換字符串里面進行向后引用,我們很快就把“朋友 of 誰誰誰”變成了“誰誰誰的朋友”。如果還要更復雜點怎么辦呢?沒有關系,replace還能接受Function作為參數作為回調函數,其中函數的第一個參數是整個匹配中的字符串,之后每一個代表個個向后引用匹配的,函數的返回值則是作為替換的字符串。所以很多使用,函數參數都會用$0, $1, $2來表示。來看個例子:
var friends ="friends of mine, friend of her and friends of his"; var result = friends.replace(/(friends?) of (\w+)/g, function($0, $1, $2) { if($2 == 'mine') $2 = 'my'; return $2 + ' ' + $1; }); document.write(result); //output: //my friends, her friend and his friends
通過回調函數就可以實現很多很負責的字符串匹配了。至於效率,就先不考慮了。
代碼運行結果:http://jsfiddle.net/BkbBD/