在 JavaScript 中,文字資料儲存在字串中。沒有單一字元的個別型態。
字串的內部格式永遠是 UTF-16,與頁面編碼無關。
引號
讓我們回顧一下引號的種類。
字串可以用單引號、雙引號或反引號括起來
let single = 'single-quoted';
let double = "double-quoted";
let backticks = `backticks`;
單引號和雙引號基本上是相同的。然而,反引號允許我們將任何表達式嵌入字串中,方法是將其包裝在 ${…}
中
function sum(a, b) {
return a + b;
}
alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3.
使用反引號的另一個優點是它們允許字串跨越多行
let guestList = `Guests:
* John
* Pete
* Mary
`;
alert(guestList); // a list of guests, multiple lines
看起來很自然,對吧?但單引號或雙引號不這樣做。
如果我們使用它們並嘗試使用多行,將會出現錯誤
let guestList = "Guests: // Error: Unexpected token ILLEGAL
* John";
單引號和雙引號來自語言創建的古代,當時沒有考慮到多行字串的需要。反引號出現得晚得多,因此更通用。
反引號還允許我們在第一個反引號之前指定一個「範本函數」。語法為:func`string`
。函數 func
會自動呼叫,接收字串和嵌入式表達式,並可以處理它們。此功能稱為「標記範本」,很少見,但您可以在 MDN 中閱讀有關它的資訊:範本字面。
特殊字元
仍然可以使用單引號和雙引號建立多行字串,方法是使用一個稱為「換行字元」的字元,寫成 \n
,表示換行
let guestList = "Guests:\n * John\n * Pete\n * Mary";
alert(guestList); // a multiline list of guests, same as above
作為一個更簡單的範例,這兩行是相等的,只是寫法不同
let str1 = "Hello\nWorld"; // two lines using a "newline symbol"
// two lines using a normal newline and backticks
let str2 = `Hello
World`;
alert(str1 == str2); // true
還有其他較不常見的特殊字元
字元 | 說明 |
---|---|
\n |
換行 |
\r |
在 Windows 文字檔案中,兩個字元 \r\n 的組合表示換行,而在非 Windows 作業系統中,它只是 \n 。這是出於歷史原因,大多數 Windows 軟體也理解 \n 。 |
\' , \" , \` |
引號 |
\\ |
反斜線 |
\t |
跳格 |
\b 、\f 、\v |
退格、換頁、垂直跳格 - 提及以求完整,來自舊時代,現在不使用了(您可以立即忘記它們)。 |
如您所見,所有特殊字元都以反斜線字元 \
開頭。它也稱為「跳脫字元」。
由於它很特別,如果我們需要在字串中顯示實際的反斜線 \
,我們需要將它加倍
alert( `The backslash: \\` ); // The backslash: \
所謂的「跳脫」引號 \'
、\"
、\`
用於將引號插入到相同引號的字串中。
例如
alert( 'I\'m the Walrus!' ); // I'm the Walrus!
如您所見,我們必須在內部引號前加上反斜線 \'
,否則它將表示字串結束。
當然,只有與封閉引號相同的引號需要跳脫。因此,作為一個更優雅的解決方案,我們可以改用雙引號或反引號
alert( "I'm the Walrus!" ); // I'm the Walrus!
除了這些特殊字元之外,還有一個 Unicode 碼 \u…
的特殊表示法,它很少使用,並在關於 Unicode 的選用章節中介紹。
字串長度
length
屬性具有字串長度
alert( `My\n`.length ); // 3
請注意,\n
是單一的「特殊」字元,因此長度確實是 3
。
length
是屬性具備其他語言背景的人們有時會誤植為呼叫 str.length()
,而非僅呼叫 str.length
。這無法運作。
請注意,str.length
是數字屬性,而非函式。無須在其後加上括號。非 .length()
,而是 .length
。
存取字元
若要取得位置 pos
的字元,請使用方括號 [pos]
或呼叫方法 str.at(pos)。第一個字元從零位置開始
let str = `Hello`;
// the first character
alert( str[0] ); // H
alert( str.at(0) ); // H
// the last character
alert( str[str.length - 1] ); // o
alert( str.at(-1) );
如您所見,.at(pos)
方法有允許負位置的優點。如果 pos
為負數,則會從字串的結尾開始計算。
因此,.at(-1)
表示最後一個字元,而 .at(-2)
表示其前一個字元,依此類推。
方括號總是會傳回負數索引的 undefined
,例如
let str = `Hello`;
alert( str[-2] ); // undefined
alert( str.at(-2) ); // l
我們也可以使用 for..of
遍歷字元
for (let char of "Hello") {
alert(char); // H,e,l,l,o (char becomes "H", then "e", then "l" etc)
}
字串是不可變的
字串無法在 JavaScript 中變更。無法變更字元。
讓我們嘗試看看它無法運作
let str = 'Hi';
str[0] = 'h'; // error
alert( str[0] ); // doesn't work
常見的解決方法是建立一個全新的字串,並將其指定給 str
,而非舊字串。
例如
let str = 'Hi';
str = 'h' + str[1]; // replace the string
alert( str ); // hi
在以下各節中,我們將看到更多此類範例。
變更大小寫
方法 toLowerCase() 和 toUpperCase() 會變更大小寫
alert( 'Interface'.toUpperCase() ); // INTERFACE
alert( 'Interface'.toLowerCase() ); // interface
或者,如果我們想要將單一字元轉換為小寫
alert( 'Interface'[0].toLowerCase() ); // 'i'
搜尋子字串
有多種方法可以在字串中尋找子字串。
str.indexOf
第一個方法是 str.indexOf(substr, pos)。
它會從指定的 pos
位置開始,在 str
中尋找 substr
,並傳回找到配對項的位置或 -1
(如果找不到任何項目)。
例如
let str = 'Widget with id';
alert( str.indexOf('Widget') ); // 0, because 'Widget' is found at the beginning
alert( str.indexOf('widget') ); // -1, not found, the search is case-sensitive
alert( str.indexOf("id") ); // 1, "id" is found at the position 1 (..idget with id)
選擇性的第二個參數允許我們從指定的位置開始搜尋。
例如,"id"
的第一次出現位於 1
位置。若要尋找下一次出現,讓我們從位置 2
開始搜尋
let str = 'Widget with id';
alert( str.indexOf('id', 2) ) // 12
如果我們對所有出現感興趣,我們可以在迴圈中執行 indexOf
。每次新的呼叫都會使用前一次配對項之後的位置
let str = 'As sly as a fox, as strong as an ox';
let target = 'as'; // let's look for it
let pos = 0;
while (true) {
let foundPos = str.indexOf(target, pos);
if (foundPos == -1) break;
alert( `Found at ${foundPos}` );
pos = foundPos + 1; // continue the search from the next position
}
可以將相同的演算法簡化
let str = "As sly as a fox, as strong as an ox";
let target = "as";
let pos = -1;
while ((pos = str.indexOf(target, pos + 1)) != -1) {
alert( pos );
}
str.lastIndexOf(substr, position)
還有一個類似的 str.lastIndexOf(substr, position) 方法,它會從字串的結尾開始搜尋到其開頭。
它會以相反的順序列出出現的項目。
indexOf
在 if
測試中會造成一點小不便。我們不能像這樣將它放入 if
中
let str = "Widget with id";
if (str.indexOf("Widget")) {
alert("We found it"); // doesn't work!
}
上述範例中的 alert
沒有顯示,因為 str.indexOf("Widget")
傳回 0
(表示它在起始位置找到匹配項)。沒錯,但 if
將 0
視為 false
。
因此,我們實際上應該檢查 -1
,如下所示
let str = "Widget with id";
if (str.indexOf("Widget") != -1) {
alert("We found it"); // works now!
}
includes、startsWith、endsWith
較新的方法 str.includes(substr, pos) 會傳回 true/false
,視 str
是否包含 substr
。
如果我們需要測試匹配項,但不需要其位置,這是正確的選擇
alert( "Widget with id".includes("Widget") ); // true
alert( "Hello".includes("Bye") ); // false
str.includes
的第二個選用引數是開始搜尋的位置
alert( "Widget".includes("id") ); // true
alert( "Widget".includes("id", 3) ); // false, from position 3 there is no "id"
方法 str.startsWith 和 str.endsWith 會執行它們所述的動作
alert( "Widget".startsWith("Wid") ); // true, "Widget" starts with "Wid"
alert( "Widget".endsWith("get") ); // true, "Widget" ends with "get"
取得子字串
JavaScript 中有 3 個方法可取得子字串:substring
、substr
和 slice
。
str.slice(start [, end])
-
傳回從
start
到(但不包含)end
的字串部分。例如
let str = "stringify"; alert( str.slice(0, 5) ); // 'strin', the substring from 0 to 5 (not including 5) alert( str.slice(0, 1) ); // 's', from 0 to 1, but not including 1, so only character at 0
如果沒有第二個引數,則
slice
會一直到字串的結尾let str = "stringify"; alert( str.slice(2) ); // 'ringify', from the 2nd position till the end
start/end
也可能為負值。它們表示位置從字串結尾計算let str = "stringify"; // start at the 4th position from the right, end at the 1st from the right alert( str.slice(-4, -1) ); // 'gif'
str.substring(start [, end])
-
傳回
start
和end
(不包含end
)之間的字串部分。這幾乎與
slice
相同,但它允許start
大於end
(在這種情況下,它只會交換start
和end
的值)。例如
let str = "stringify"; // these are same for substring alert( str.substring(2, 6) ); // "ring" alert( str.substring(6, 2) ); // "ring" // ...but not for slice: alert( str.slice(2, 6) ); // "ring" (the same) alert( str.slice(6, 2) ); // "" (an empty string)
與
slice
不同,不支援負引數,它們會視為0
。 str.substr(start [, length])
-
傳回從
start
開始,具有指定length
的字串部分。與前述方法不同,此方法允許我們指定
length
,而不是結束位置let str = "stringify"; alert( str.substr(2, 4) ); // 'ring', from the 2nd position get 4 characters
第一個引數可以為負值,以從結尾計算
let str = "stringify"; alert( str.substr(-4, 2) ); // 'gi', from the 4th position get 2 characters
此方法位於語言規範的 附錄 B 中。這表示只有瀏覽器託管的 Javascript 引擎應該支援它,不建議使用它。實際上,它在各處都受到支援。
讓我們回顧這些方法,以避免混淆
方法 | 選擇… | 負數 |
---|---|---|
slice(start, end) |
從 start 到 end (不包含 end ) |
允許負數 |
substring(start, end) |
在 start 和 end 之間(不包含 end ) |
負值表示 0 |
substr(start, length) |
從 start 取得 length 個字元 |
允許負 start |
所有這些都可以完成工作。正式來說,substr
有個小缺點:它不是在 JavaScript 核心規格中描述,而是在附錄 B 中,其中涵蓋了主要出於歷史原因而存在的僅瀏覽器功能。因此,非瀏覽器環境可能無法支援它。但在實務上,它在所有地方都可以使用。
在其他兩個變體中,slice
稍微靈活一些,它允許負數引數,而且寫起來較短。
因此,對於實際用途,只要記住 slice
就足夠了。
比較字串
正如我們從章節 比較 中所知,字串會按照字母順序逐字元比較。
儘管如此,還是有些奇怪的地方。
-
小寫字母永遠大於大寫字母
alert( 'a' > 'Z' ); // true
-
帶有變音符號的字母「不按順序」
alert( 'Österreich' > 'Zealand' ); // true
如果我們對這些國家名稱進行排序,這可能會導致奇怪的結果。通常人們會希望
Zealand
在清單中出現在Österreich
之後。
要了解發生了什麼事,我們應該知道 Javascript 中的字串使用 UTF-16 編碼。也就是說:每個字元都有對應的數字代碼。
有特殊的方法可以取得代碼的字元,反之亦然
str.codePointAt(pos)
-
傳回一個十進位數字,代表位置
pos
的字元代碼// different case letters have different codes alert( "Z".codePointAt(0) ); // 90 alert( "z".codePointAt(0) ); // 122 alert( "z".codePointAt(0).toString(16) ); // 7a (if we need a hexadecimal value)
String.fromCodePoint(code)
-
根據數字
code
建立一個字元alert( String.fromCodePoint(90) ); // Z alert( String.fromCodePoint(0x5a) ); // Z (we can also use a hex value as an argument)
現在讓我們透過建立一個字串來檢視代碼為 65..220
的字元(拉丁字母和一些額外的字元)
let str = '';
for (let i = 65; i <= 220; i++) {
str += String.fromCodePoint(i);
}
alert( str );
// Output:
// ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
// ¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜ
看到了嗎?大寫字元排在前面,然後是幾個特殊字元,再接著是小寫字元,而 Ö
則在輸出的尾端。
現在很明顯為什麼 a > Z
了。
字元是根據其數字代碼進行比較。較大的代碼表示較大的字元。a
的代碼(97)大於 Z
的代碼(90)。
- 所有小寫字母都出現在大寫字母之後,因為它們的代碼較大。
- 像
Ö
這樣的某些字母與主字母表不同。在此,它的代碼大於a
到z
之間的任何代碼。
正確的比較
執行字串比較的「正確」演算法比看起來複雜,因為不同的語言有不同的字母表。
因此,瀏覽器需要知道要比較的語言。
幸運的是,現代瀏覽器支援國際化標準 ECMA-402。
它提供了一個特殊的方法來比較不同語言的字串,遵循它們的規則。
呼叫 str.localeCompare(str2) 會傳回一個整數,表示根據語言規則,str
是小於、等於或大於 str2
- 如果
str
小於str2
,則傳回負數。 - 如果
str
大於str2
,則傳回正數。 - 如果它們相等,則傳回
0
。
例如
alert( 'Österreich'.localeCompare('Zealand') ); // -1
此方法實際上還有兩個額外的參數,在 文件 中指定,它允許指定語言(預設從環境中取得,字母順序取決於語言)並設定其他規則,例如大小寫敏感或 "a"
和 "á"
是否應視為相同等。
摘要
- 有 3 種引號。反引號允許字串跨越多行並內嵌表達式
${…}
。 - 我們可以使用特殊字元,例如換行
\n
。 - 要取得字元,請使用:
[]
或at
方法。 - 要取得子字串,請使用:
slice
或substring
。 - 要將字串轉換為小寫/大寫,請使用:
toLowerCase/toUpperCase
。 - 要尋找子字串,請使用:
indexOf
,或includes/startsWith/endsWith
進行簡單檢查。 - 要根據語言比較字串,請使用:
localeCompare
,否則它們將按字元碼比較。
字串中有許多其他有用的方法
str.trim()
– 從字串的開頭和結尾移除(“修剪”)空格。str.repeat(n)
– 重複字串n
次。- …更多內容請參閱 手冊。
字串也有使用正規表示法進行搜尋/取代的方法。但那是個大主題,因此在單獨的教學單元 正規表示法 中說明。
此外,現在重要的是要知道字串是基於 Unicode 編碼,因此比較會有問題。章節 Unicode、字串內部 中有更多關於 Unicode 的資訊。
留言