Javascript 字符串

2023-02-17 10:44 更新

在 JavaScript 中,文本數(shù)據(jù)被以字符串形式存儲,單個字符沒有單獨(dú)的類型。

字符串的內(nèi)部格式始終是 UTF-16,它不依賴于頁面編碼。

引號(Quotes)

讓我們回憶一下引號的種類。

字符串可以包含在單引號、雙引號或反引號中:

let single = 'single-quoted';
let double = "double-quoted";

let backticks = `backticks`;

單引號和雙引號基本相同。但是,反引號允許我們通過 ${…} 將任何表達(dá)式嵌入到字符串中:

function sum(a, b) {
  return a + b;
}

alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3.

使用反引號的另一個優(yōu)點(diǎn)是它們允許字符串跨行:

let guestList = `Guests:
 * John
 * Pete
 * Mary
`;

alert(guestList); // 客人清單,多行

看起來很自然,不是嗎?但是單引號和雙引號可不能這樣做。

如果我們使用單引號或雙引號來實(shí)現(xiàn)字符串跨行的話,則會出現(xiàn)錯誤:

let guestList = "Guests: // Error: Unexpected token ILLEGAL
  * John";

單引號和雙引號來自語言創(chuàng)建的的古老時代,當(dāng)時沒有考慮到多行字符串的需要。反引號出現(xiàn)較晚,因此更通用。

反引號還允許我們在第一個反引號之前指定一個“模版函數(shù)”。語法是:func`string`。函數(shù) func 被自動調(diào)用,接收字符串和嵌入式表達(dá)式,并處理它們。你可以在 docs 中閱讀更多關(guān)于它們的信息。這叫做 “tagged templates”。此功能可以更輕松地將字符串包裝到自定義模版或其他函數(shù)中,但這很少使用。

特殊字符

我們?nèi)匀豢梢酝ㄟ^使用“換行符(newline character)”,以支持使用單引號和雙引號來創(chuàng)建跨行字符串。換行符寫作 \n,用來表示換行:

let guestList = "Guests:\n * John\n * Pete\n * Mary";

alert(guestList); // 一個多行的客人列表

例如,這兩行描述的是一樣的,只是書寫方式不同:

let str1 = "Hello\nWorld"; // 使用“換行符”創(chuàng)建的兩行字符串

// 使用反引號和普通的換行創(chuàng)建的兩行字符串
let str2 = `Hello
World`;

alert(str1 == str2); // true

還有其他不常見的“特殊”字符。

這是完整列表:

字符 描述
\n 換行
\r 在 Windows 文本文件中,兩個字符 \r\n 的組合代表一個換行。而在非 Windows 操作系統(tǒng)上,它就是 \n。這是歷史原因造成的,大多數(shù)的 Windows 軟件也理解 \n。
\'\" 引號
\\ 反斜線
\t 制表符
\b\f\v 退格,換頁,垂直標(biāo)簽 —— 為了兼容性,現(xiàn)在已經(jīng)不使用了。
\xXX 具有給定十六進(jìn)制 Unicode XX 的 Unicode 字符,例如:'\x7A' 和 'z' 相同。
\uXXXX 以 UTF-16 編碼的十六進(jìn)制代碼 XXXX 的 Unicode 字符,例如 \u00A9 —— 是版權(quán)符號 ? 的 Unicode。它必須正好是 4 個十六進(jìn)制數(shù)字。
\u{X…XXXXXX}(1 到 6 個十六進(jìn)制字符) 具有給定 UTF-32 編碼的 Unicode 符號。一些罕見的字符用兩個 Unicode 符號編碼,占用 4 個字節(jié)。這樣我們就可以插入長代碼了。

Unicode 示例:

alert( "\u00A9" ); // ?
alert( "\u{20331}" ); // 佫,罕見的中國象形文字(長 Unicode)
alert( "\u{1F60D}" ); // ,笑臉符號(另一個長 Unicode)

所有的特殊字符都以反斜杠字符 \ 開始。它也被稱為“轉(zhuǎn)義字符”。

如果我們想要在字符串中插入一個引號,我們也會使用它。

例如:

alert( 'I\'m the Walrus!' ); // I'm the Walrus!

正如你所看到的,我們必須在內(nèi)部引號前加上反斜杠 \',否則它將表示字符串結(jié)束。

當(dāng)然,只有與外部閉合引號相同的引號才需要轉(zhuǎn)義。因此,作為一個更優(yōu)雅的解決方案,我們可以改用雙引號或者反引號:

alert( `I'm the Walrus!` ); // I'm the Walrus!

注意反斜杠 \ 在 JavaScript 中用于正確讀取字符串,然后消失。內(nèi)存中的字符串沒有 \。你從上述示例中的 alert 可以清楚地看到這一點(diǎn)。

但是如果我們需要在字符串中顯示一個實(shí)際的反斜杠 \ 應(yīng)該怎么做?

我們可以這樣做,只需要將其書寫兩次 \\

alert( `The backslash: \\` ); // The backslash: \

字符串長度

length 屬性表示字符串長度:

alert( `My\n`.length ); // 3

注意 \n 是一個單獨(dú)的“特殊”字符,所以長度確實(shí)是 3。

?length ?是一個屬性

掌握其他編程語言的人,有時會錯誤地調(diào)用 str.length() 而不是 str.length。這是行不通的。

請注意 str.length 是一個數(shù)字屬性,而不是函數(shù)。后面不需要添加括號。

訪問字符

要獲取在 pos 位置的一個字符,可以使用方括號 [pos] 或者調(diào)用 str.charAt(pos) 方法。第一個字符從零位置開始:

let str = `Hello`;

// 第一個字符
alert( str[0] ); // H
alert( str.charAt(0) ); // H

// 最后一個字符
alert( str[str.length - 1] ); // o

方括號是獲取字符的一種現(xiàn)代化方法,而 charAt 是歷史原因才存在的。

它們之間的唯一區(qū)別是,如果沒有找到字符,[] 返回 undefined,而 charAt 返回一個空字符串:

let str = `Hello`;

alert( str[1000] ); // undefined
alert( str.charAt(1000) ); // ''(空字符串)

我們也可以使用 for..of 遍歷字符:

for (let char of "Hello") {
  alert(char); // H,e,l,l,o(char 變?yōu)?"H",然后是 "e",然后是 "l" 等)
}

字符串是不可變的

在 JavaScript 中,字符串不可更改。改變字符是不可能的。

我們證明一下為什么不可能:

let str = 'Hi';

str[0] = 'h'; // error
alert( str[0] ); // 無法運(yùn)行

通常的解決方法是創(chuàng)建一個新的字符串,并將其分配給 str 而不是以前的字符串。

例如:

let str = 'Hi';

str = 'h' + str[1];  // 替換字符串

alert( str ); // hi

在接下來的章節(jié),我們將看到更多相關(guān)示例。

改變大小寫

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,因?yàn)?'Widget' 一開始就被找到
alert( str.indexOf('widget') ); // -1,沒有找到,檢索是大小寫敏感的

alert( str.indexOf("id") ); // 1,"id" 在位置 1 處(……idget 和 id)

可選的第二個參數(shù)允許我們從一個給定的位置開始檢索。

例如,"id" 第一次出現(xiàn)的位置是 1。查詢下一個存在位置時,我們從 2 開始檢索:

let str = 'Widget with id';

alert( str.indexOf('id', 2) ) // 12

如果我們對所有存在位置都感興趣,可以在一個循環(huán)中使用 indexOf。每一次新的調(diào)用都發(fā)生在上一匹配位置之后:

let str = 'As sly as a fox, as strong as an ox';

let target = 'as'; // 這是我們要查找的目標(biāo)

let pos = 0;
while (true) {
  let foundPos = str.indexOf(target, pos);
  if (foundPos == -1) break;

  alert( `Found at ${foundPos}` );
  pos = foundPos + 1; // 繼續(xù)從下一個位置查找
}

相同的算法可以簡寫:

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, pos)

還有一個類似的方法 str.lastIndexOf(substr, position),它從字符串的末尾開始搜索到開頭。

它會以相反的順序列出這些事件。

在 if 測試中 indexOf 有一點(diǎn)不方便。我們不能像這樣把它放在 if 中:

let str = "Widget with id";

if (str.indexOf("Widget")) {
    alert("We found it"); // 不工作!
}

上述示例中的 alert 不會顯示,因?yàn)?nbsp;str.indexOf("Widget") 返回 0(意思是它在起始位置就查找到了匹配項(xiàng))。是的,但是 if 認(rèn)為 0 表示 false。

因此我們應(yīng)該檢查 -1,像這樣:

let str = "Widget with id";

if (str.indexOf("Widget") != -1) {
    alert("We found it"); // 現(xiàn)在工作了!
}

按位(bitwise)NOT 技巧

這里使用的一個老技巧是 bitwise NOT ~ 運(yùn)算符。它將數(shù)字轉(zhuǎn)換為 32-bit 整數(shù)(如果存在小數(shù)部分,則刪除小數(shù)部分),然后對其二進(jìn)制表示形式中的所有位均取反。

實(shí)際上,這意味著一件很簡單的事兒:對于 32-bit 整數(shù),~n 等于 -(n+1)。

例如:

alert( ~2 ); // -3,和 -(2+1) 相同
alert( ~1 ); // -2,和 -(1+1) 相同
alert( ~0 ); // -1,和 -(0+1) 相同
alert( ~-1 ); // 0,和 -(-1+1) 相同

正如我們看到這樣,只有當(dāng) n == -1 時,~n 才為零(適用于任何 32-bit 帶符號的整數(shù) n)。

因此,僅當(dāng) indexOf 的結(jié)果不是 -1 時,檢查 if ( ~str.indexOf("...") ) 才為真。換句話說,當(dāng)有匹配時。

人們用它來簡寫 indexOf 檢查:

let str = "Widget";

if (~str.indexOf("Widget")) {
  alert( 'Found it!' ); // 正常運(yùn)行
}

通常不建議以非顯而易見的方式使用語言特性,但這種特殊技巧在舊代碼中仍被廣泛使用,所以我們應(yīng)該理解它。

只要記?。?code>if (~str.indexOf(...)) 讀作 “if found”。

確切地說,由于 ~ 運(yùn)算符將大數(shù)字截斷為 32 位,因此存在給出 0 的其他數(shù)字,最小的數(shù)字是 ~4294967295=0。這使得這種檢查只有在字符串沒有那么長的情況下才是正確的。

現(xiàn)在我們只會在舊的代碼中看到這個技巧,因?yàn)楝F(xiàn)代 JavaScript 提供了 .includes 方法(見下文)。

includes,startsWith,endsWith

更現(xiàn)代的方法 str.includes(substr, pos) 根據(jù) str 中是否包含 substr 來返回 true/false。

如果我們需要檢測匹配,但不需要它的位置,那么這是正確的選擇:

alert( "Widget with id".includes("Widget") ); // true

alert( "Hello".includes("Bye") ); // false

str.includes 的第二個可選參數(shù)是開始搜索的起始位置:

alert( "Widget".includes("id") ); // true
alert( "Widget".includes("id", 3) ); // false, 從位置 3 開始沒有 "id"

方法 str.startsWith 和 str.endsWith 的功能與其名稱所表示的意思相同:

alert( "Widget".startsWith("Wid") ); // true,"Widget" 以 "Wid" 開始
alert( "Widget".endsWith("get") ); // true,"Widget" 以 "get" 結(jié)束

獲取子字符串

JavaScript 中有三種獲取字符串的方法:substringsubstr 和 slice。

?str.slice(start [, end])
?

返回字符串從 start 到(但不包括)end 的部分。

例如:

let str = "stringify";
alert( str.slice(0, 5) ); // 'strin',從 0 到 5 的子字符串(不包括 5)
alert( str.slice(0, 1) ); // 's',從 0 到 1,但不包括 1,所以只有在 0 處的字符

如果沒有第二個參數(shù),slice 會一直運(yùn)行到字符串末尾:

let str = "stringify";
alert( str.slice(2) ); // 從第二個位置直到結(jié)束

start/end 也有可能是負(fù)值。它們的意思是起始位置從字符串結(jié)尾計(jì)算:

let str = "stringify";

// 從右邊的第四個位置開始,在右邊的第一個位置結(jié)束
alert( str.slice(-4, -1) ); // 'gif'

?str.substring(start [, end])
?

返回字符串從 start 到(但不包括)end 的部分。

這與 slice 幾乎相同,但它允許 start 大于 end。

例如:

let str = "stringify";

// 這些對于 substring 是相同的
alert( str.substring(2, 6) ); // "ring"
alert( str.substring(6, 2) ); // "ring"

// ……但對 slice 是不同的:
alert( str.slice(2, 6) ); // "ring"(一樣)
alert( str.slice(6, 2) ); // ""(空字符串)

不支持負(fù)參數(shù)(不像 slice),它們被視為 0。

?str.substr(start [, length])
?

返回字符串從 start 開始的給定 length 的部分。

與以前的方法相比,這個允許我們指定 length 而不是結(jié)束位置:

let str = "stringify";
alert( str.substr(2, 4) ); // 'ring',從位置 2 開始,獲取 4 個字符

第一個參數(shù)可能是負(fù)數(shù),從結(jié)尾算起:

let str = "stringify";
alert( str.substr(-4, 2) ); // 'gi',從第 4 位獲取 2 個字符

我們回顧一下這些方法,以免混淆:

方法 選擇方式…… 負(fù)值參數(shù)
slice(start, end) 從 start 到 end(不含 end 允許
substring(start, end) 從 start 到 end(不含 end 負(fù)值被視為 0
substr(start, length) 從 start 開始獲取長為 length 的字符串 允許 start 為負(fù)數(shù)

使用哪一個?

它們都可用于獲取子字符串。正式一點(diǎn)來講,substr 有一個小缺點(diǎn):它不是在 JavaScript 核心規(guī)范中描述的,而是在附錄 B 中。附錄 B 的內(nèi)容主要是描述因歷史原因而遺留下來的僅瀏覽器特性。因此,理論上非瀏覽器環(huán)境可能無法支持 substr,但實(shí)際上它在別的地方也都能用。

相較于其他兩個變體,slice 稍微靈活一些,它允許以負(fù)值作為參數(shù)并且寫法更簡短。因此僅僅記住這三種方法中的 slice 就足夠了。

比較字符串

正如我們從 值的比較 一章中了解到的,字符串按字母順序逐字比較。

不過,也有一些奇怪的地方。

  1. 小寫字母總是大于大寫字母:
  2. alert( 'a' > 'Z' ); // true
  3. 帶變音符號的字母存在“亂序”的情況:
  4. alert( '?sterreich' > 'Zealand' ); // true

    如果我們對這些國家名進(jìn)行排序,可能會導(dǎo)致奇怪的結(jié)果。通常,人們會期望 Zealand 在名單中的 ?sterreich 之后出現(xiàn)。

為了明白發(fā)生了什么,我們回顧一下在 JavaScript 中字符串的內(nèi)部表示。

所有的字符串都使用 UTF-16 編碼。即:每個字符都有對應(yīng)的數(shù)字代碼。有特殊的方法可以獲取代碼表示的字符,以及字符對應(yīng)的代碼。

?str.codePointAt(pos)
?

返回在 pos 位置的字符代碼 :

// 不同的字母有不同的代碼
alert( "z".codePointAt(0) ); // 122
alert( "Z".codePointAt(0) ); // 90

?String.fromCodePoint(code)
?

通過數(shù)字 code 創(chuàng)建字符

alert( String.fromCodePoint(90) ); // Z

我們還可以用 \u 后跟十六進(jìn)制代碼,通過這些代碼添加 Unicode 字符:

// 在十六進(jìn)制系統(tǒng)中 90 為 5a
alert( '\u005a' ); // Z

現(xiàn)在我們看一下代碼為 65..220 的字符(拉丁字母和一些額外的字符),方法是創(chuàng)建一個字符串:

let str = '';

for (let i = 65; i <= 220; i++) {
  str += String.fromCodePoint(i);
}
alert( str );
// ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~?????
// ?¢£¤¥|§¨?a??-?ˉ°±23′μ?·?1o?????àá??????èéê?ìí??D?òó???×?ùú?ü

看到?jīng)]?先是大寫字符,然后是一些特殊字符,然后是小寫字符,而 ? 幾乎是最后輸出。

現(xiàn)在很明顯為什么 a > Z

字符通過數(shù)字代碼進(jìn)行比較。越大的代碼意味著字符越大。a(97)的代碼大于 Z(90)的代碼。

  • 所有小寫字母追隨在大寫字母之后,因?yàn)樗鼈兊拇a更大。
  • 一些像 ??? 的字母與主要字母表不同。這里,它的代碼比任何從 ?a? 到 ?z? 的代碼都要大。

正確的比較

執(zhí)行字符串比較的“正確”算法比看起來更復(fù)雜,因?yàn)椴煌Z言的字母都不相同。

因此瀏覽器需要知道要比較的語言。

幸運(yùn)的是,所有現(xiàn)代瀏覽器(IE10- 需要額外的庫 Intl.JS) 都支持國際化標(biāo)準(zhǔn) ECMA-402

它提供了一種特殊的方法來比較不同語言的字符串,遵循它們的規(guī)則。

調(diào)用 str.localeCompare(str2) 會根據(jù)語言規(guī)則返回一個整數(shù),這個整數(shù)能指示字符串 str 在排序順序中排在字符串 str2 前面、后面、還是相同:

  • 如果 ?str ?排在 ?str2 ?前面,則返回負(fù)數(shù)。
  • 如果 ?str ?排在 ?str2 ?后面,則返回正數(shù)。
  • 如果它們在相同位置,則返回 ?0?。

例如:

alert( '?sterreich'.localeCompare('Zealand') ); // -1

這個方法實(shí)際上在 文檔 中指定了兩個額外的參數(shù),這兩個參數(shù)允許它指定語言(默認(rèn)語言從環(huán)境中獲取,字符順序視語言不同而不同)并設(shè)置諸如區(qū)分大小寫,或應(yīng)該將 "a" 和 "a?" 作相同處理等附加的規(guī)則。

內(nèi)部,Unicode

進(jìn)階內(nèi)容

這部分會深入字符串內(nèi)部。如果你計(jì)劃處理 emoji、罕見的數(shù)學(xué)或象形文字或其他罕見的符號,這些知識會對你有用。

如果你不打算支持它們,你可以跳過這一部分。

代理對

所有常用的字符都是一個 2 字節(jié)的代碼。大多數(shù)歐洲語言,數(shù)字甚至大多數(shù)象形文字中的字母都有 2 字節(jié)的表示形式。

但 2 字節(jié)只允許 65536 個組合,這對于表示每個可能的符號是不夠的。所以稀有的符號被稱為“代理對”的一對 2 字節(jié)的符號編碼。

這些符號的長度是 ?2?:

alert( ''.length ); // 2,大寫數(shù)學(xué)符號 X
alert( ''.length ); // 2,笑哭表情
alert( ''.length ); // 2,罕見的中國象形文字

注意,代理對在 JavaScript 被創(chuàng)建時并不存在,因此無法被編程語言正確處理!

我們實(shí)際上在上面的每個字符串中都有一個符號,但 length 顯示長度為 2。

String.fromCodePoint 和 str.codePointAt 是幾種處理代理對的少數(shù)方法。它們最近才出現(xiàn)在編程語言中。在它們之前,只有 String.fromCharCode 和  str.charCodeAt。這些方法實(shí)際上與 fromCodePoint/codePointAt 相同,但是不適用于代理對。

獲取符號可能會非常麻煩,因?yàn)榇韺Ρ徽J(rèn)為是兩個字符:

alert( ''[0] ); // 奇怪的符號……
alert( ''[1] ); // ……代理對的一塊

請注意,代理對的各部分沒有任何意義。因此,上述示例中的 alert 顯示的實(shí)際上是垃圾信息。

技術(shù)角度來說,代理對也是可以通過它們的代碼檢測到的:如果一個字符的代碼在 0xd800..0xdbff 范圍內(nèi),那么它是代理對的第一部分。下一個字符(第二部分)必須在 0xdc00..0xdfff 范圍中。這些范圍是按照標(biāo)準(zhǔn)專門為代理對保留的。

在上述示例中:

// charCodeAt 不理解代理對,所以它給出了代理對的代碼

alert( ''.charCodeAt(0).toString(16) ); // d835,在 0xd800 和 0xdbff 之間
alert( ''.charCodeAt(1).toString(16) ); // dcb3, 在 0xdc00 和 0xdfff 之間

本章節(jié)后面的 Iterable object(可迭代對象) 章節(jié)中,你可以找到更多處理代理對的方法??赡苡袑iT的庫,這里沒有什么足夠好的建議了。

變音符號與規(guī)范化

在許多語言中,都有一些由基本字符組成的符號,在其上方/下方有一個標(biāo)記。

例如,字母 a 可以是 àáa???ā 的基本字符。最常見的“復(fù)合”字符在 UTF-16 表中都有自己的代碼。但不是全部,因?yàn)榭赡艿慕M合太多。

為了支持任意組合,UTF-16 允許我們使用多個 Unicode 字符:基本字符緊跟“裝飾”它的一個或多個“標(biāo)記”字符。

例如,如果我們 S 后跟有特殊的 “dot above” 字符(代碼 \u0307),則顯示 S?。

alert( 'S\u0307' ); // S?

如果我們需要在字母上方(或下方)添加額外的標(biāo)記 —— 沒問題,只需要添加必要的標(biāo)記字符即可。

例如,如果我們追加一個字符 “dot below”(代碼 \u0323),那么我們將得到“S 上面和下面都有點(diǎn)”的字符:S??。

例如:

alert( 'S\u0307\u0323' ); // S??

這在提供良好靈活性的同時,也存在一個有趣的問題:兩個視覺上看起來相同的字符,可以用不同的 Unicode 組合表示。

例如:

let s1 = 'S\u0307\u0323'; // S??,S + 上點(diǎn) + 下點(diǎn)
let s2 = 'S\u0323\u0307'; // S??,S + 下點(diǎn) + 上點(diǎn)

alert( `s1: ${s1}, s2: ${s2}` );

alert( s1 == s2 ); // false,盡管字符看起來相同(?!)

為了解決這個問題,有一個 “Unicode 規(guī)范化”算法,它將每個字符串都轉(zhuǎn)化成單個“通用”格式。

它由 str.normalize() 實(shí)現(xiàn)。

alert( "S\u0307\u0323".normalize() == "S\u0323\u0307".normalize() ); // true

有趣的是,在實(shí)際情況下,normalize() 實(shí)際上將一個由 3 個字符組成的序列合并為一個:\u1e68(S 有兩個點(diǎn))。

alert( "S\u0307\u0323".normalize().length ); // 1

alert( "S\u0307\u0323".normalize() == "\u1e68" ); // true

事實(shí)上,情況并非總是如此,因?yàn)榉?nbsp;S?? 是“常用”的,所以 UTF-16 創(chuàng)建者把它包含在主表中并給它了對應(yīng)的代碼。

如果你想了解更多關(guān)于規(guī)范化規(guī)則和變體的信息 —— 它們在 Unicode 標(biāo)準(zhǔn)附錄中有詳細(xì)描述:Unicode 規(guī)范化形式,但對于大多數(shù)實(shí)際目的來說,本文的內(nèi)容就已經(jīng)足夠了。

總結(jié)

  • 有 3 種類型的引號。反引號允許字符串跨越多行并可以使用 ?${…}? 在字符串中嵌入表達(dá)式。
  • JavaScript 中的字符串使用的是 UTF-16 編碼。
  • 我們可以使用像 ?\n? 這樣的特殊字符或通過使用 ?\u...? 來操作它們的 Unicode 進(jìn)行字符插入。
  • 獲取字符時,使用 ?[]?。
  • 獲取子字符串,使用 ?slice ?或 ?substring?。
  • 字符串的大/小寫轉(zhuǎn)換,使用:?toLowerCase/toUpperCase?。
  • 查找子字符串時,使用 ?indexOf ?或 ?includes/startsWith/endsWith? 進(jìn)行簡單檢查。
  • 根據(jù)語言比較字符串時使用 ?localeCompare?,否則將按字符代碼進(jìn)行比較。

還有其他幾種有用的字符串方法:

  • ?str.trim()? —— 刪除字符串前后的空格 (“trims”)。
  • ?str.repeat(n)? —— 重復(fù)字符串 ?n? 次。
  • ……更多內(nèi)容細(xì)節(jié)請參見 手冊

字符串還具有使用正則表達(dá)式進(jìn)行搜索/替換的方法。但這個話題很大,因此我們將在本教程中單獨(dú)的 正則表達(dá)式 章節(jié)中進(jìn)行討論。

任務(wù)


首字母大寫

重要程度: 5

寫一個函數(shù) ucFirst(str),并返回首字母大寫的字符串 str,例如:

ucFirst("john") == "John";

解決方案

我們不能“替換”第一個字符,因?yàn)樵?JavaScript 中字符串是不可變的。

但是我們可以根據(jù)已有字符串創(chuàng)建一個首字母大寫的新字符串:

let newStr = str[0].toUpperCase() + str.slice(1);

這里存在一個小問題。如果 str 是空的,那么 str[0] 就是 undefined,但由于 undefined 并沒有 toUpperCase() 方法,因此我們會得到一個錯誤。

存在如下兩種變體:

  1. 使用 ?str.charAt(0)?,因?yàn)樗偸菚祷匾粋€字符串(可能為空)。
  2. 為空字符添加測試。

這是第二種變體:

function ucFirst(str) {
  if (!str) return str;

  return str[0].toUpperCase() + str.slice(1);
}

alert( ucFirst("john") ); // John

檢查 spam

重要程度: 5

寫一個函數(shù) checkSpam(str),如果 str 包含 viagra 或 XXX 就返回 true,否則返回 false

函數(shù)必須不區(qū)分大小寫:

checkSpam('buy ViAgRA now') == true
checkSpam('free xxxxx') == true
checkSpam("innocent rabbit") == false

解決方案

為了使搜索不區(qū)分大小寫,我們將字符串改為小寫,然后搜索:

function checkSpam(str) {
  let lowerStr = str.toLowerCase();

  return lowerStr.includes('viagra') || lowerStr.includes('xxx');
}

alert( checkSpam('buy ViAgRA now') );
alert( checkSpam('free xxxxx') );
alert( checkSpam("innocent rabbit") );

截斷文本

重要程度: 5

創(chuàng)建函數(shù) truncate(str, maxlength) 來檢查 str 的長度,如果超過 maxlength —— 應(yīng)使用 "…" 來代替 str 的結(jié)尾部分,長度仍然等于 maxlength。

函數(shù)的結(jié)果應(yīng)該是截斷后的文本(如果需要的話)。

例如:

truncate("What I'd like to tell on this topic is:", 20) = "What I'd like to te…"

truncate("Hi everyone!", 20) = "Hi everyone!"

解決方案

最大長度必須是 maxlength,因此為了給省略號留空間我們需要縮短它。

請注意,省略號實(shí)際上有一個單獨(dú)的 Unicode 字符,而不是三個點(diǎn)。

function truncate(str, maxlength) {
  return (str.length > maxlength) ?
    str.slice(0, maxlength - 1) + '…' : str;
}

提取貨幣

重要程度: 4

我們有以 "$120" 這樣的格式表示的花銷。意味著:先是美元符號,然后才是數(shù)值。

創(chuàng)建函數(shù) extractCurrencyValue(str) 從字符串中提取數(shù)值并返回。

例如:

alert( extractCurrencyValue('$120') === 120 ); // true

解決方案

function extractCurrencyValue(str) {
  return +str.slice(1);
}


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號