(JS基础)String 类型

2,750 阅读10分钟

我们对字符串类型的数据并不会陌生。let str = 'value'就是一个最简单的生成字符串例子。

我们还可以通过String(thing)new String(thing)将任何类型的数据转化成字符串。注意,new String(thing)会生成一个String类型的对象将字符串包裹。

例如,把一个空对象传入,String({})返回的值为"[object Object]",这里不得不提的就是,其他类型默认的toString()方法本质就是调用String({})

字符串量能直接使用String的属性和方法,如'string'.toLowerCase()。其实语言内部会将'string'封装成对象,再执行相应的方法,最后字符串对象使用valueOf()toString()方法得到字符串的值。


属性

常用属性只有一个:length,返回字符串的长度

方法

以下介绍的,除了String​.raw()为静态方法,其他都是字符串对象的方法。所有的字符串方法都不会对原字符串修改,都是返回一个新的字符串或结果。

拼接/补全

查询

截取

  • slice(beginSlice[, endSlice]):返回从原字符串截取指定索引的片段beginSlice表示作为开始字符的索引,endSlice表示结束位置的索引,负数表示从后数起。
  • substring(indexStart[, indexEnd])与slice()基本一致,但indexEnd不能为负数。
  • split([separator[, limit]]):使用指定的分隔符字符串将原字符串分割成字符串数组separator表示作为分隔符的字符串或正则表达式;limit表示字符串数组的最大长度。
  • trim():返回一个将原字符的两端删除空白字符
  • trim​Right():返回一个将原字符的右端删除空白字符
  • trim​Left():返回一个将原字符的左端删除空白字符
  • chartAt(index):从原字符串中返回指定索引的字符
  • replace(regexp|substr, newSubStr|function)返回原字符串以特定规则被替换后的字符串。当前字符串不变。

转换大小写

字符码点

  • normalize([form]):按照指定的一种 Unicode 正规形式将当前字符串正规化form默认为 "NFC"。例子:'\u01D1'.normalize() === '\u004F\u030C'.normalize();  // true
  • charCodeAt(index):返回指定索引的 UTF-16 代码单元值的数字;如果索引超出范围,则返回NaN。对于都是"基本平面"的字符串,索引值与其所在位置相同;但对于含有"辅助平面"的字符串来说就要注意,如𝌆为 4 字节字符,需要两个 UTF-16 代码单元表示,'𝌆a'.charCodeAt(2)的值为 97 ,而非NaN,而索引 1 和 2 的值分别为𝌆的高位和低位的码点数值。
  • codePointAt(index):就我个人测试来看,charCodeAt()方法返回的结果一致,除了索引超出范围返回的是undefined

内置迭代器([Symbol.iterator])

String对象内置Iterator对象,通过属性[Symbol.iterator]获取,在遍历时调用,如for...of运算、展开运算符(...)等。

获取迭代器

let strIterator = 'abcde'[Symbol.iterator]();
console.log(strIterator.next().value);  // 'a'
console.log(strIterator.next().value);  // 'b'
console.log(strIterator.next().value);  // 'c'

修改迭代器

// 必须创建字符串对象
let str = new String('abcde');
// 原生迭代器效果
for (let i of str) {
  console.log(i);
}
// 打印结果: a  b  c  d  e
// 自定义迭代器函数
str[Symbol.iterator] = function* () {
  yield 1;
  yield 2;
  yield 3;
}
// 测试自定义迭代器
console.log(...str);  // 1 2 3


字符串模板(Template literals)

字符串模板用"反引号"表示,即 ` `

基本用法

模板字符串内,字符串量可以直接写,${ }内执行JavaScrip代码。用于生成一个带运算的字符串结果。

let num1 = 123,
  num2 = 456;
let temp = `hello ${num1 + num2} world !`;
console.log(temp);    // "hello 579 world !"

标签模板(Tagged templates)用法

我们一般调用函数的方式是fn(['hello ', 'world'], 999),函数名后用括号包裹参数。而标签模板可以这样写:fn`hello ${args}world`,函数名后直接跟``。注意,这两种方式的参数是一致的。

分解:

${ }的模板字符串,内部其实是被${}分割成多份。以`hello ${999}world${666} !`为例:

`
hello 
${999}
world
${666} 
!
`

${ }的外面是字符串直接量,而${ }之间是 js 代码。如此看来,字符串模板被分成了 5 份,而字符串直接量会组成字符串数组,所以传入函数时是 3 个参数,分别是['hello ', 'world', ' !']999666。下面给出简单例子说明。

// 用于查看传参情况的函数
function fn() {
  console.log(arguments[0])
}
fn`hello ${999}world${666} !`;
// 下图看结果

String​.raw()

谈到模板字符串,不得不谈String对象的一个静态方法:String​.raw()。一般都是以标签函数的方式调用它,即String​.raw``,当然,你可以使用普通函数的()方式传参,只不过要按照上面额规则手动转化一下。它的用途是获取一个模板字符串的原始字面量值,简单说就是把所有的反斜杠(\)转义。

String.raw `Hi\u000A!`;      // "Hi\\u000A!"
String.raw `Hi\n${999}!`;    // "Hi\\n999!"
String.raw`\\`;              // "\\\\"


字符编码与字符集

基本概念

  1. 字符,是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。
  2. 字库表,是一个相当于所有可读或者可显示字符的数据库,字库表决定了整个字符集能够展现表示的所有字符的范围。其实就是字符的集合
  3. 编码字符集,简称字符集,用一个编码值code point(又称码点)来表示一个字符(即该字符在子库表中的位置),这个值称为字符对应于编码字符集(如:Unicode、ASCII)的序号。
  4. 字符编码,是编码字符集和实际存储数值之间的转换关系。

用商场储物柜为例子。

字符,等于单个柜箱,存放着我们想看的内容。

整个储物柜就是字库表

为每个柜箱按顺序贴上标签后,整个储物柜就是编码字符集,每个数字(码点)对应一个柜箱(字符)。

我们手上有一份查询表格,但表格上只能写二进制或十六进制的数字。以什么样的方式把储物柜上的数字对应到表格上(映射),就是字符编码

ASCII

ASCII 既是字符集,也是字符编码。用一个字节的长度存储字符。自行百度,不多介绍。

Unicode

Unicode 是一个字符集,为每个符号指定一个编号,即"码点"(code point)。其目标是将全世界所有的字符包含在一个集合里,计算机只要支持这一个字符集,就能显示所有的字符。

每个区可以存放 65536 个(216)字符,称为一个平面(plane)。

目前,一共有 17 个(25)平面,也就是说,整个 Unicode 字符集的大小现在是 221

最前面的 65536 个字符位,称为基本平面(缩写 BMP ),它的码点范围是从 0 一直到 216-1 ,写成 16 进制就是从 U+0000 ~ U+FFFF

剩下的字符都放在辅助平面(缩写 SMP ),码点范围为 U+010000 ~ U+10FFFF

UTF-32

UTF-32 是字符编码方式,用固定长度的 4 字节表示一个字符,与 Unicode 字节内容一一对应码点。例如:

U+597D = 0x0000 597D

但缺点很明显,浪费空间。HTML5 标准就明文规定,网页不得编码成UTF-32。

UTF-8

UTF-8 是一种变长的编码方法,字符长度从 1 个字节到 4 个字节不等。越是常用的字符,字节越短,最前面的 128 个字符,只使用 1 个字节表示,与 ASCII 码完全相同。如,编码范围在0x0000 - 0x007F只占用 1 字节,而0x010000 - 0x10FFFF要占用 4 字节。是最常见的网页编码。

由于 UTF-8 是变长的,若不直到其长度,连续的字符则无法解析。如何判断其长度,这就涉及它的编码规则

  1. 对于单字节的符号,第一位为 0 ,用二进制表示为0xxx xxxx
  2. 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

阮一峰的博文的图表展示:

Unicode 符号范围 (十六进制) UTF-8 编码方式 (二进制) 字节
0000 0000-0000 007F 0xxxxxxx 1
0000 0080-0000 07FF 110xxxxx 10xxxxxx 2
0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx 3
0001 0000-0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 4

图表中x组合而成的就是实际的 Unicode 码点。

UTF-16

UTF-16 编码介于 UTF-32 与 UTF-8 之间,同时结合了定长和变长两种编码方法的特点。编码规则就是,基本平面的字符占用 2 个字节辅助平面的字符占用 4 个字节

因为在基本平面内,从U+D800 ~ U+DFFF是一个空段,空间为 211 ,即 2 个 210 。而一个辅助平面的字符需要的空间为 220 ,刚好可以拆分成两个空段内的字符,高位(H)映射在U+D800 ~ U+DBFF低位(L)映射在U+DC00 ~ U+DFFF。因此解决了 4 字节的字符的识别问题。

所以,对于基本平面,Unicode 与 UTF-16 码相同。

对于辅助平面,Unicode 与 UTF-16 的转化公式如下:

H = Math.floor((c-0x10000) / 0x400) + 0xD800
L = (c - 0x10000) % 0x400 + 0xDC00

不难理解,辅助平面的码点是从U+10000开始,则减去0x10000;上面提到高低位各占 10 位,则除以0x400得到的商和余数则分别位高低位的起点值,最后高位加上基础值0xD800,低位加上0xDC00得到目标值。(内容总结自阮一峰的博文

JavaScript 使用的编码方式

JavaScript 语言采用 Unicode 字符集,但是只支持一种编码方法,就是 UCS-2 。由于历史原因(阮一峰的博文有介绍),UCS-2 只支持 2 字节的字符,4 字节的字符被当成 2 个 2 字节的字符解析。现在已经没有 UCS-2 。

ES6 增强对 Unicode 的支持

  1. ES6 可以自动识别 4 字节的码点,如console.log('\ud834\udf06');  // 𝌆
  2. 允许直接用码点表示Unicode字符,如'𝌆' === '\u{1d306}';  // true。2 字节的字符可以省略{}
  3. ES6 新增了几个专门处理 4 字节码点的函数(看上方介绍)。
  4. ES6 的正则表达式提供了u修饰符,对正则表达式添加 4 字节码点的支持。例子:/^.$/.test('𝌆');  // false/^.$/u.test('𝌆'); // true