在 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),它从字符串的末尾向开头搜索。
它会按相反的顺序列出出现位置。
if
测试中的 indexOf
存在一个轻微的不便。我们不能像这样将其放入 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) 根据 str
是否包含 substr
返回 true/false
。
如果我们需要测试匹配项,但不需要其位置,那么这是正确的选择
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 的内容。
评论
<code>
标记,对于多行,请将其包装在<pre>
标记中,对于超过 10 行,请使用沙箱(plnkr、jsbin、codepen…)