4. String

1. 通用知识

在计算机编程中,字符串通常是一个字符序列,可以是字面量,也可以是变量。后者可能会允许修改元素和改变长度,也可能是固定的(在创建之后)。

字符串是一种数据类型,通常被实现为 bytes/characters/code units 的数组,存储着元素序列,元素通常是字符且会使用某种字符编码。用“数组”实现是为了快速访问单个单元的字符和子字符串,也有个别编程语言是用链表来实现的(比如 Haskell)。

字符串通常由字符组成。可用于存储人类可读的数据,如句子、按字母顺序排列的数据列表。

根据使用的编程语言和其提供的具体数据类型,声明为字符串的变量在内存中的存储,可能是静态分配预留了最大长度,也可能是动态分配以确保元素的数量是可变的。

字符串是一种非常重要且有用的数据类型,几乎所有的编程语言都实现了它。在不同的编程语言里,字符串可能是原始类型,也可能是复合类型。当字符串以字面量的形式出现在源代码中的时候,被称为是字符串字面量或匿名字符串。

定长+变长、字符集+字符编码、原始+复合类型、字面量

2. String 类型

在 JavaScript 里,String 类型用来表示文本数据,它是一组 16 位无符号整数值的“元素”。

字符串里的每个元素都占了一个位置,我们可以通过下标来访问每个元素,下标从 0 开始。字符串的长度就是它里面元素的个数。如下:

let foo = 'JavaScript';
foo[0];  // "J"
foo[1];  // "a"
foo.length;  // 10

3. String 对象

在 JavaScript 中,String 对象是 String 原始值的对象包装器,用来表示和操作字符序列。

3.1 构造器

  1. new String() 返回对象,搭配 new 关键字作为构造函数用

  2. String() 返回字符串,直接当函数用可做类型转换(此方式更有用)

3.2 静态方法

  1. String.fromCharCode() 传 Unicode 值序列

  2. String.fromCodePoint() 传代码点序列

  3. String.raw() 传原始模板字符串

3.3 实例属性

  1. String.prototype.length 只读

3.4 实例方法

String.prototype.xxx

3.4.1 通用方法

  1. valueOf()

  2. toString()

3.4.2 常用操作

  1. 下标→单个字符

    • at()

    • charAt()

    • charCodeAt()

    • codePointAt()

  2. 字符串→下标

    • indexOf()

    • lastIndexOf()

  3. 是否以指定字符串开始、结束、包含

    • startsWith()

    • endsWith()

    • includes()

  4. 提取子串,传起始下标

    • slice()

    • substring()

    • substr() 已不推荐使用

  5. 正则表达式

    • search()

    • match(), matchAll()

    • replace(), replaceAll()

  6. split() 变数组

  7. concat() 拼接

  8. 大小写

    • toLowerCase()

    • toUpperCase()

    • toLocaleLowerCase()

    • toLocaleUpperCase()

  9. 去首尾空格

    • trim()

    • trimStart()

    • trimEnd()

  10. 前后对齐

    • padEnd()

    • padStart()

3.4.3 其它

  1. normalize() 返回 Unicode 正规形式

  2. repeat() 传重复次数

  3. localeCompare() 比较

  4. @@iterator() 迭代器

4. 常见操作

4.1 创建字符串

创建字符串原始值,可以使用字符串字面量和函数。如下:

let str1 = 'hello world';  // 单引号,字面量
let str2 = "hello world";  // 双引号,字面量
let str3 = `hello world`;  // 字符串模板,字面量

let str4 = String('hello world');  // 函数

创建字符串对象,可以使用字符串对象的构造器。如下:

let str5 = new String('hello world');  // new
console.log(str5.valueOf());  // hello world

4.2 原始值和对象

在大多数情况下,字符串原始值和字符串对象是可以互换使用的。JavaScript 会自动把字符串原始值转成对象,以便在字符串原始值上使用字符串对象的方法。

但是,字符串原始值和字符串对象,还是有些区别的。

区别一:typeof 的结果不同

typeof 'hello' === 'string';                // true
typeof (new String('hello')) === 'object';  // true

区别二:eval() 的结果不同

let str1 = '1+2';
let str2 = new String('1+2');
console.log(eval(str1));  // 会输出数字 3
console.log(eval(str2));  // 会输出字符串 '1+2'  

当然,可以通过手动调用 valueOf() 来“修复”。如下:

console.log(eval(str2.valueOf()));  // 此时会输出数字 3

4.3 访问单个字符

方法一:使用 charAt()

console.log('hello'.charAt(1));  // "e"

方法二:使用 []。这是在 ES5 里引入的,它把字符串视为类数组(array-like)对象,然后单个字符对应一个数字索引。

console.log('hello'[1]);  // "e"

当使用[]进行字符访问的时候,尝试删除或者重新赋值是不会生效的,因为所涉及的属性是不可写、不可配置的。

4.4 字符串比较

可以用运算符>< 以及实例方法 localeCompare()

这里需要注意的是,用==运算符比较的两个字符串是大小写敏感的。如果不想区分大小写,可以先都转成大写再用===。如下:

function isEqual(str1, str2){
    return str1.toUpperCase() === str2.toUpperCase();
}

isEqual('javascript', 'JavaScript');  // 会返回 true

这里用了toUpperCase()而不是toLowerCase(),是考虑到某些 UTF-8 字符转换的问题。

4.5 类型转换

.toString()相比,String()更可靠,因为它还适用于 undefined, null 和 symbol。

5. 注意事项

5.1 长字符串字面量

方法一:使用操作符 +。如下:

let longStr1 = 'This is a very very long string ' +
               'which needs to wrap across multiple lines ' +
               'because otherwise your code is unreadable.'

方法二:在行尾加 \,以表示该字符串还会在下一行继续。如下:

let longStr2 = 'This is a very very long string \
which needs to wrap across multiple lines \
because otherwise your code is unreadable.'

注意:在\之后不能有空格、注释和其它任何字符(换行符除外),否则它就不工作了。

5.2 输出多行字符串

可使用模板字符串,它支持直接换行。

console.log(`This is a very very long string 
which needs to wrap across multiple lines 
because otherwise your code is unreadable.`);

源代码里的任何字符(包括换行符)都是模板字面量的一部分,所以上面的字符串里是含有字符的。

5.3 慎用字符串模拟数组

在实际使用中,我们可能会用字符串来模拟数组/列表,但这么做是有潜在风险的。

要么分隔符可能就是列表里的元素本身,要么就得挑个特殊的字符来当分隔符。前者可能会导致列表不工作,后者不仅要约定特殊字符而且还会增加后续的维护成本。

所以,尽量用字符串表示文本数据。

5.4 字符集和编码方式

这个话题较为独立,详见《Unicode 字符编码模型》

6. 主要参考

Last updated