字符串可以通过字符串字面量创建为原始值,也可以通过 String() 构造函数创建为字符串对象:
js
const string1 = "A string primitive";
const string2 = 'Also a string primitive';
const string3 = `Yet another string primitive`;
js
const string4 = new String("A String object");
字符串原始值和字符串对象共享很多行为,但也有其他重要的区别和注意事项。请参阅下面的“字符串原始值和字符串对象”。
字符串字面量可以使用单引号或者双引号指定,它们的处理方式相同,或者使用反引号字符 `。最后一种形式指定了模板字符串,你可以在其中插入表达式。有关字符串字面量语法的更多信息,请参见词法语法。
有两种方式访问字符串中的单个字符。首先是 charAt() 方法:
js
"cat".charAt(1); // gives value "a"
另一个方式是将字符串视为类数组对象,其中各个字符对应于一个数字索引:
js
"cat"[1]; // gives value "a"
当使用方括号表示法进行字符串访问时,尝试删除或为其赋值的行为将不成功。涉及的属性既不可写(writable)也不可配置(configurable)(更多细节,请参见 Object.defineProperty())。
使用小于和大于运算符来比较字符串:
js
const a = "a";
const b = "b";
if (a < b) {
// true
console.log(`${a} 小于 ${b}`);
} else if (a > b) {
console.log(`${a} 大于 ${b}`);
} else {
console.log(`${a} 和 ${b} 相等`);
}
注意,所有的比较运算符(包括 === 和 ==)在比较字符串时都区分大小写。不区分大小写地比较字符串的常见方式是在比较它们之前将它们转为相同的大小写(大写或者小写)。
js
function areEqualCaseInsensitive(str1, str2) {
return str1.toUpperCase() === str2.toUpperCase();
}
通过 toUpperCase() 或 toLowerCase() 进行转换的选择大多是任意的,并且在扩展到拉丁字母之外时,两者都不是完全可靠的。例如,德语小写字母 ß 和 ss 都被 toUpperCase() 转换为 SS,而土耳其字母 ı 会被 toLowerCase() 错误地报告为不等于 I,除非使用 toLocaleLowerCase("tr")。
js
const areEqualInUpperCase = (str1, str2) =>
str1.toUpperCase() === str2.toUpperCase();
const areEqualInLowerCase = (str1, str2) =>
str1.toLowerCase() === str2.toLowerCase();
areEqualInUpperCase("ß", "ss"); // true;应该为 false
areEqualInLowerCase("ı", "I"); // false;应该为 true
用于测试不区分大小写的相等性的本地化(locale-aware)且可靠的解决方案是使用 Intl.Collator API 或者字符串的 localeCompare() 方法——它们共享相同的接口——sensitivity 选项设置为 "accent" 或 "base"。
js
const areEqual = (str1, str2, locale = "en-US") =>
str1.localeCompare(str2, locale, { sensitivity: "accent" }) === 0;
areEqual("ß", "ss", "de"); // false
areEqual("ı", "I", "tr"); // true
localeCompare() 方法使字符串比较可以像 strcmp() 函数一样进行——它允许以本地化的方式对字符串进行排序。
注意,JavaScript 区分 String 对象和原始字符串值(Boolean 和 Number 也是如此)。
字符串字面量(使用单引号或者双引号表示)和从非构造函数上下文中的 String 调用返回的字符串(即在不使用 new 关键字的情况下调用)是原始字符串。在需要调用原始字符串的方法或进行属性查找的上下文中,JavaScript 将自动包装原始字符串并在包装对象上调用方法或执行属性查找。
js
const strPrim = "foo"; // 字面量是一个字符串原始值
const strPrim2 = String(1); // 被强制转换为字符串原始值“1”
const strPrim3 = String(true); // 被强制转换为字符串原始值“true”
const strObj = new String(strPrim); // 使用 `new` 关键字调用 `String` 构造函数返回一个字符串包装对象。
console.log(typeof strPrim); // "string"
console.log(typeof strPrim2); // "string"
console.log(typeof strPrim3); // "string"
console.log(typeof strObj); // "object"
警告:你应该基本不会将 String 作为构造函数使用。
使用 eval() 时,字符串原始值和 String 对象也会给出不同的结果。传递给 eval 的原始值被当作源代码处理;而 String 对象则被当作对象处理,返回对象。例如:
js
const s1 = "2 + 2"; // 创建一个字符串原始值
const s2 = new String("2 + 2"); // 创建一个 String 对象
console.log(eval(s1)); // 返回数字 4
console.log(eval(s2)); // 返回字符串“2 + 2”
因此,当一段代码在期望使用字符串原始值的时候却使用了 String 对象是,可能会导致执行失败,虽然一般情况下,我们并不需要考虑这样的问题。
String 对象始终可以使用 valueOf() 方法将其转换为对应的原始值。
js
console.log(eval(s2.valueOf())); // 返回数字 4
许多内置操作首先将它们的参数强制转换为字符串(这就是为什么 String 对象的行为类似于字符串原始值的原因)。这些操作可以总结为以下几点:
有几种方法可以在 JavaScript 中实现几乎相同的效果。
- 模板字符串:
`${x}` 为嵌入的表达式执行上面的字符串强制转换步骤。
String() 函数:String(x) 使用相同的算法去转换 x,只是 Symbol 不会抛出 TypeError,而是返回 "Symbol(description)",其中 description 是对 Symbol 的描述。
- 使用
+ 运算符:"" + x 将其操作数强制转为原始值,而不是字符串,并且对于某些对象,其行为与普通字符串强制转换完全不同。有关更多细节,请参见其参考页。
根据你使用的情况,你可能想要使用 `${x}`(模拟内置行为)或 String(x)(处理 symbol 值而不抛出错误),但你不应该使用 "" + x。
字符串基本上表示为 UTF-16 码元的序列。在 UTF-16 编码中,每个码元都是 16 位长。这意味着最多有 216 个或 65536 个可能的字符可表示为单个 UTF-16 码元。该字符集称为基本多语言平面(BMP),包含最常见的字符,如拉丁字母、希腊字母、西里尔字母以及许多东亚字符。每个码元都可以用以 \u 开头的 4 个十六进制数字写在一个字符串中。
然而,整个 Unicode 字符集比 65536 大得多。额外的字符以代理对(surrogate pair)的形式存储在 UTF-16 中,代理对是一对 16 位码元,表示一个单个字符。为了避免歧义,配对的两个部分必须介于 0xD800 和 0xDFFF 之间,并且这些码元不用于编码单码元字符。(更准确地说,前导代理,也称为高位代理,其值在 0xD800 和 0xDBFF 之间(含),而后尾代理,也称为低位代理,其值在 0xDC00 和 0xDFFF 之间(含)。)每个 Unicode 字符由一个或者两个 UTF-16 码元组成,也称为 Unicode 码位(code point)。每个 Unicode 码位都可以使用 \u{xxxxxx} 写成一个字符串,其中 xxxxxx 表示 1–6 个十六进制数字。
“单独代理项(lone surrogate)”是指满足以下描述之一的 16 位码元:
- 它在范围
0xD800 到 0xDBFF 内(含)(即为前导代理),但它是字符串中的最后一个码元,或者下一个码元不是后尾代理。
- 它在范围
0xDC00 到 0xDFFF 内(含)(即为后尾代理),但它是字符串中的第一个码元,或者前一个码元不是前导代理。
单独代理项不代表任何 Unicode 字符。尽管大多数 JavaScript 内置方法都可以正确处理它们,因为它们都是基于 UTF-16 码元工作的,但是在与其他系统交互时,单独代理项通常不是有效的值。例如,encodeURI() 会为单独代理项抛出 URIError,因为 URI 编码使用 UTF-8 编码,而 UTF-8 没有任何编码单独代理项的方法。不包含任何单独代理项的字符串称为规范的字符串,并且可以安全地与不处理 UTF-16 的函数一起使用,例如 encodeURI() 或 TextEncoder 。你可以使用 isWellFormed() 方法检查字符串是否规范,或使用 toWellFormed() 方法清除单独代理项。
除了 Unicode 字符之外,还有某些 Unicode 字符序列应视为一个视觉单元,被称为字素簇(grapheme cluster)。最常见的情况是 emoji:许多具有多种变体的 emoji 实际上是由多个 emoji 组成的,通常由 <ZWJ>(U+200D)字符连接。
你必须小心迭代字符级别。例如,split("") 将按照 UTF-16 码元分割并将代理对分开。字符串索引也是指的每个 UTF-16 码元的索引。另一方面,[Symbol.iterator]() 按 Unicode 码位迭代。遍历字素簇将需要一些自定义代码。
js
"😄".split(""); // ['\ud83d', '\ude04']; splits into two lone surrogates
// "Backhand Index Pointing Right: Dark Skin Tone"
[..."👉🏿"]; // ['👉', '🏿']
// splits into the basic "Backhand Index Pointing Right" emoji and
// the "Dark skin tone" emoji
// "Family: Man, Boy"
[..."👨👦"]; // [ '👨', '', '👦' ]
// splits into the "Man" and "Boy" emoji, joined by a ZWJ
// The United Nations flag
[..."🇺🇳"]; // [ '🇺', '🇳' ]
// splits into two "region indicator" letters "U" and "N".
// All flag emojis are formed by joining two region indicator letters
String()
-
创建一个新的 String 对象。它在作为函数调用时执行类型转换,而不是作为构造函数调用,后者通常更有用。
可以使用 String 作为 toString() 更可靠的代替方法,因为它在用于 null 和 undefined 时仍然有效。例如:
js
const nullVar = null;
nullVar.toString(); // TypeError: nullVar is null
String(nullVar); // "null"
const undefinedVar = undefined;
undefinedVar.toString(); // TypeError: undefinedVar is undefined
String(undefinedVar); // "undefined"