第 12 章字串
目錄
購買書籍
(廣告,請勿封鎖)

第 12 章字串

字串是 JavaScript 字元的不可變序列。每個字元都是一個 16 位元的 UTF-16 編碼單位。這表示單一 Unicode 字元由一個或兩個 JavaScript 字元表示。只要計算字元或分割字串(請參閱 第 24 章),您主要需要擔心兩個字元的案例。

字串文字

單引號和雙引號都可以用來 界定字串文字:

'He said: "Hello"'
"He said: \"Hello\""

'Everyone\'s a winner'
"Everyone's a winner"

因此,您可以自由使用任何一種引號。不過,有幾個考量因素

  • 社群中最常見的風格是對 HTML 使用雙引號,對 JavaScript 使用單引號。
  • 另一方面,某些語言(例如 C 和 Java)專門對字串使用雙引號。因此,在多語言程式碼庫中使用它們可能是合理的。
  • 對於 JSON(在 第 22 章 中討論),您必須使用雙引號。

如果您一致地使用引號,您的程式碼看起來會更簡潔。但是,有時不同的引號表示您不必跳脫,這可以證明您不那麼一致(例如,您通常可能使用單引號,但暫時切換到雙引號來撰寫前一個範例的最後一個)。

在字串文字中跳脫

字串文字中的大多數字元 僅代表它們自己。反斜線用於 跳脫,並啟用數個特殊功能:

換行

您可以透過使用反斜線跳脫行尾(換行字元,換行符)來將字串分佈在多行:

var str = 'written \
over \
multiple \
lines';
console.log(str === 'written over multiple lines'); // true

另一種方法是使用 加號運算子來串接:

var str = 'written ' +
          'over ' +
          'multiple ' +
          'lines';
字元跳脫序列

這些 序列以反斜線開頭:

  • 控制字元:\b 是退格,\f 是換頁,\n 是換行,\r 是回車,\t 是水平定位標籤,\v 是垂直定位標籤。
  • 表示自己的轉義字元:\' 是單引號,\" 是雙引號,\\ 是反斜線。除了 b f n r t v x u 和十進位數字之外的所有字元也都表示自己。以下是兩個範例

    > '\"'
    '"'
    > '\q'
    'q'
NUL 字元(Unicode 編碼點 0)
此字元由 \0 表示。
十六進位轉義序列

\xHHHH 是兩個十六進位數字)透過 ASCII 編碼指定字元。例如:

> '\x4D'
'M'
Unicode 轉義序列

\uHHHHHHHH 是四個十六進位數字)指定 UTF-16 編碼單位(請參閱 第 24 章 Unicode 和 JavaScript)。以下是兩個範例

> '\u004D'
'M'
> '\u03C0'
'π'

字元存取

有兩個運算會傳回字串的第 n 個字元。[16] 請注意 JavaScript 沒有字元的特殊資料類型;這些運算會傳回字串

> 'abc'.charAt(1)
'b'
> 'abc'[1]
'b'

有些舊瀏覽器不支援透過方括弧陣列式存取字元。

轉換為字串

值會轉換為字串,如下所示:

結果

未定義

'undefined'

null

'null'

布林值

false'false'

true'true'

數字

數字以字串形式表示(例如,3.141'3.141'

字串

與輸入相同(無需轉換)

物件

呼叫 ToPrimitive(value, String)(請參閱 演算法:ToPrimitive()—將值轉換為基本型別),並轉換產生的基本型別。

手動轉換為字串

將任何值轉換為字串的三種最常見方式為:

String(value)

(以函數呼叫,而非建構函數)

''+value

value.toString()

(不適用於 undefinednull!)

我偏好 String(),因為它較具描述性。以下是幾個範例

> String(false)
'false'
> String(7.35)
'7.35'
> String({ first: 'John', last: 'Doe' })
'[object Object]'
> String([ 'a', 'b', 'c' ])
'a,b,c'

請注意,對於顯示資料,JSON.stringify() (JSON.stringify(value, replacer?, space?)) 通常比正規轉換為字串效果更好:

> console.log(JSON.stringify({ first: 'John', last: 'Doe' }))
{"first":"John","last":"Doe"}
> console.log(JSON.stringify([ 'a', 'b', 'c' ]))
["a","b","c"]

當然,您必須了解 JSON.stringify() 的限制,它並非總是顯示所有內容。例如,它會隱藏它無法處理的值的屬性(函數等)。好處是,其輸出可以由 eval() 解析,而且它可以將深度巢狀資料顯示為格式良好的樹狀結構。

陷阱:轉換無法反轉

考量到 JavaScript 自動轉換的頻率,令人遺憾的是轉換並非總是可反轉,特別是布林值:

> String(false)
'false'
> Boolean('false')
true

對於 undefinednull,我們面臨類似的問題。

比較字串

有兩種比較字串的方法。首先,您可以使用比較運算子: <>===<=>=它們有以下缺點:

  • 它們區分大小寫

    > 'B' > 'A'  // ok
    true
    > 'B' > 'a'  // should be true
    false
  • 它們無法妥善處理變音符號和重音符號

    > 'ä' < 'b'  // should be true
    false
    > 'é' < 'f'  // should be true
    false

其次,您可以使用 String.prototype.localeCompare(other)它往往表現得更好,但並非總是受支援(請參閱 搜尋和比較 以取得詳細資料)。以下是 Firefox 主控台中的互動

> 'B'.localeCompare('A')
2
> 'B'.localeCompare('a')
2

> 'ä'.localeCompare('b')
-2
> 'é'.localeCompare('f')
-2

小於零的結果表示接收器「小於」引數。大於零的結果表示接收器「大於」引數。

連接字串

有兩種主要方法可連接字串。

連接:加號 (+) 算子

當運算元之一為字串時,+ 算子會執行字串連接。如果您想在變數中收集字串片段,複合賦值運算元 += 會很有用:

> var str = '';
> str += 'Say hello ';
> str += 7;
> str += ' times fast!';
> str
'Say hello 7 times fast!'

連接:加入字串片段陣列

看起來先前的做法會在每次新增片段到 str 時建立一個新字串。較舊的 JavaScript 引擎會這樣做,這表示您可以先將所有片段收集到陣列中,最後再將它們連接起來,以提升字串連接效能:

> var arr = [];

> arr.push('Say hello ');
> arr.push(7);
> arr.push(' times fast');

> arr.join('')
'Say hello 7 times fast'

不過,較新的引擎會透過 + 最佳化字串連接,並在內部使用類似的方法。因此,加號運算元在這些引擎上會比較快。

String 函式

可以用兩種方式呼叫 String 函式:

String(value)

作為一般函式,它會將 value 轉換為原始字串(請參閱 轉換為字串

> String(123)
'123'
> typeof String('abc')  // no change
'string'
new String(str)

作為建構函式,它會建立 String 的新執行個體(請參閱 原始資料的包裝物件),一個包裝 str 的物件(非字串會強制轉換為字串)。例如

> typeof new String('abc')
'object'

前者呼叫比較常見。

String 建構函式方法

String.fromCharCode(codeUnit1, codeUnit2, ...) 會產生一個字串,其字元為 16 位元無號整數 codeUnit1codeUnit2 等指定的 UTF-16 編碼單位。例如:

> String.fromCharCode(97, 98, 99)
'abc'

如果您想將數字陣列轉換為字串,您可以透過 apply() 執行(請參閱 func.apply(thisValue, argArray)

> String.fromCharCode.apply(null, [97, 98, 99])
'abc'

String.fromCharCode() 的反函數是 String.prototype.charCodeAt()

String 執行個體屬性長度

length 屬性會指出字串中 JavaScript 字元的數量,而且不可變更:

> 'abc'.length
3

String 原型方法

原始字串的所有方法都儲存在 String.prototype 中(請參閱 原始類型從包裝類型借用其方法)。接下來,我將說明它們如何用於原始字串,而不是 String 的實例。

擷取子字串

下列方法從接收器擷取子字串

String.prototype.charAt(pos)

傳回一個字串,其中包含位置 pos 的字元。例如:

> 'abc'.charAt(1)
'b'

下列兩個表達式傳回相同的結果,但有些較舊的 JavaScript 引擎只支援 charAt() 來存取字元

str.charAt(n)
str[n]
String.prototype.charCodeAt(pos)

傳回位置 pos 的 JavaScript 字元(UTF-16 編碼單位;請參閱 第 24 章)的代碼(16 位元無符號整數)。

以下是 建立字元代碼陣列 的方式:

> 'abc'.split('').map(function (x) { return x.charCodeAt(0) })
[ 97, 98, 99 ]

charCodeAt() 的反函式是 String.fromCharCode()

String.prototype.slice(start, end?)

傳回從位置 start 開始到位置 end(不含)的子字串。 兩個參數都可以是負數,然後字串的 length 會加到它們上面:

> 'abc'.slice(2)
'c'
> 'abc'.slice(1, 2)
'b'
> 'abc'.slice(-2)
'bc'
String.prototype.substring(start, end?)
應避免使用,而改用類似的 slice(),它可以處理負數位置,而且在各個瀏覽器中實作更一致。
String.prototype.split(separator?, limit?)

擷取接收器中由 separator 分隔的子字串,並將它們傳回陣列中。此方法有兩個參數:

  • separator:字串或正規表示式。如果遺漏,會傳回完整的字串,並將其包在陣列中。
  • limit:如果提供,傳回的陣列會包含最多 limit 個元素。

以下是一些範例

> 'a,  b,c, d'.split(',')  // string
[ 'a', '  b', 'c', ' d' ]
> 'a,  b,c, d'.split(/,/)  // simple regular expression
[ 'a', '  b', 'c', ' d' ]
> 'a,  b,c, d'.split(/, */)   // more complex regular expression
[ 'a', 'b', 'c', 'd' ]
> 'a,  b,c, d'.split(/, */, 2)  // setting a limit
[ 'a', 'b' ]
> 'test'.split()  // no separator provided
[ 'test' ]

如果有一個群組,則也會將匹配項傳回為陣列元素

> 'a,  b  ,  '.split(/(,)/)
[ 'a', ',', '  b  ', ',', '  ' ]
> 'a,  b  ,  '.split(/ *(,) */)
[ 'a', ',', 'b', ',', '' ]

使用 ''(空字串)作為分隔符號,產生一個包含字串字元的陣列

> 'abc'.split('')
[ 'a', 'b', 'c' ]

轉換

前一節是關於擷取子字串,本節是關於將給定的字串轉換成新的字串。這些方法通常如下使用:

var str = str.trim();

換句話說,原始字串在被(非破壞性地)轉換後會被捨棄

String.prototype.trim()

移除 字串開頭和結尾的所有空白:

> '\r\nabc \t'.trim()
'abc'
String.prototype.concat(str1?, str2?, ...)

傳回接收者和 str1str2 等的串接:

> 'hello'.concat(' ', 'world', '!')
'hello world!'
String.prototype.toLowerCase()

建立一個新的 字串,其中所有原始字串的字元都轉換成小寫:

> 'MJÖLNIR'.toLowerCase()
'mjölnir'
String.prototype.toLocaleLowerCase()
作用和 toLowerCase() 相同,但會遵守目前區域設定的規則。根據 ECMAScript 規範:「只有在少數情況(例如土耳其語)中,該語言的規則與一般 Unicode 大小寫對應衝突時,才會有所不同。」
String.prototype.toUpperCase()

建立一個 新的字串,其中所有原始字串的字元都轉換成大寫:

> 'mjölnir'.toUpperCase()
'MJÖLNIR'
String.prototype.toLocaleUpperCase()
作用和 toUpperCase() 相同,但會遵守目前區域設定的規則。

搜尋和比較

下列方法 用於搜尋和比較字串:

String.prototype.indexOf(searchString, position?)

position(預設為 0)開始搜尋 searchString。它會傳回找到 searchString 的位置,或找不到時傳回 –1

> 'aXaX'.indexOf('X')
1
> 'aXaX'.indexOf('X', 2)
3

請注意,在字串中尋找文字時,正規表示式也能發揮同樣的作用。例如,下列兩個表達式是等效的:

str.indexOf('abc') >= 0
/abc/.test(str)
String.prototype.lastIndexOf(searchString, position?)

position(預設為結尾)開始向後搜尋 searchString它會傳回找到 searchString 的位置,或找不到時傳回 –1:

> 'aXaX'.lastIndexOf('X')
3
> 'aXaX'.lastIndexOf('X', 2)
1
String.prototype.localeCompare(other)

執行字串與 other 的區域設定敏感 比較。它會傳回一個數字:

  • < 0 如果字串出現在 other 之前
  • = 0 如果字串等於 other
  • > 0 如果字串出現在 other 之後

例如

> 'apple'.localeCompare('banana')
-2
> 'apple'.localeCompare('apple')
0

警告

並非所有 JavaScript 引擎都能正確實作此方法。有些僅根據比較運算子來實作。不過,ECMAScript 國際化 API(請參閱 ECMAScript 國際化 API)確實提供 Unicode 感知實作。亦即,如果引擎中提供該 API,則 localeCompare() 會運作。

如果支援,localeCompare() 會比比較運算子更適合用來比較字串。請參閱 比較字串 以取得更多資訊。

使用正規表示式進行測試、比對和取代

下列方法使用正規表示式

String.prototype.search(regexp)(在 String.prototype.search:比對在哪個索引位置? 中有更詳細的說明)

傳回 regexp 在接收者中比對到的第一個索引(如果沒有比對到,則傳回 –1):

> '-yy-xxx-y-'.search(/x+/)
4
String.prototype.match(regexp)(在 String.prototype.match:擷取群組或傳回所有比對到的子字串 中有更詳細的說明)

比對接收者與指定的正規表示式。如果 regexp 的旗標 /g 未設定,則會傳回第一個比對的比對物件:

> '-abb--aaab-'.match(/(a+)b/)
[ 'ab',
  'a',
  index: 1,
  input: '-abb--aaab-' ]

如果旗標 /g 已設定,則會在陣列中傳回所有完整的比對(群組 0)

> '-abb--aaab-'.match(/(a+)b/g)
[ 'ab', 'aaab' ]
String.prototype.replace(search, replacement)(在 String.prototype.replace:搜尋和取代 中有更詳細的說明)

搜尋 search 並以 replacement 取代。search 可以是字串或正規表示式,而 replacement 可以是字串或函式。除非您使用正規表示式作為 search,且其旗標 /g 已設定,否則只會取代第一次出現的字串

> 'iixxxixx'.replace('i', 'o')
'oixxxixx'
> 'iixxxixx'.replace(/i/, 'o')
'oixxxixx'
> 'iixxxixx'.replace(/i/g, 'o')
'ooxxxoxx'

取代字串中的美元符號 ($) 可讓您參考完整的比對或擷取的群組:

> 'iixxxixx'.replace(/i+/g, '($&)') // complete match
'(ii)xxx(i)xx'
> 'iixxxixx'.replace(/(i+)/g, '($1)') // group 1
'(ii)xxx(i)xx'

您也可以透過函式來計算取代

> function repl(all) { return '('+all.toUpperCase()+')' }
> 'axbbyyxaa'.replace(/a+|b+/g, repl)
'(A)x(BB)yyx(AA)'



[16] 嚴格來說,JavaScript 字串包含 UTF-16 碼單位的序列。亦即,JavaScript 字元是 Unicode 碼單位(請參閱 第 24 章)。

下一頁:13. 陳述式