本文内容
UTF-16 和 Unicode
Unicode 为全世界所有的字符提供了一个对应的值,例如 a 对应 1,b 对应 2..."人" 对应 2333(以上的值都是我瞎编的),它仅仅是规定了字符和它对应的值。
而 UTF-8、UTF-16、UTF-32 这些,都是属于字符编码,它们的作用是用 8 位、16位、32位 等来表示一个 Unicode 字符(也就是用 1、2、4 个字节来表示 Unicode 里规定的码值)
显然,字符那么多,8 位不可能表示完世界上所有的字符,所以 UTF-8 提供了一种扩展机制,8 位最多表示 256 种状态,UTF-8 会留下一部分的值,标识为一个扩展。例如当我们读取一个字节,发现读取到的是 255 时,代表这一个 UTF-8 编码还没有将一个字符编码完全,需要再读一个 UTF-8 来确定剩下的编码。(255 是我编的,但是肯定是读取到某些值就当做一个扩展码位)
也就是说,会使用多个 UTF-8 码位来表示一个字符(称之为代理对)。同样的 UTF-16 也一样面临这个问题,因为字符总量超过了 65536 个,16 位也无法表示所有字符,UTF-16 依然需要扩展码位(代理对)。现代来说,UTF-32 是不需要扩展码位的,但是所有的字符都用 32 位表示,存储会有所浪费。
关于码位的 API
在 JavaScript 中,字符串都使用 UTF-16 编码,str.length 返回的是字符串的码位数量,因此,对于某些生僻的字符,str.length 的数量可能与字符串的字符数量不一致,例如:
let txt = "😁";
console.log(txt.length); // 2
console.log(txt.charAt(0)); // �
console.log(txt.charAt(1)); // �
console.log(txt.charCodeAt(0)); // 55357
console.log(txt.charCodeAt(1)); // 56833
console.log(txt.codePointAt(0)); // 128513
console.log(txt.codePointAt(1)); // 56833
这个 😁 字符是通过 2 个 UTF-16 代理对表示的,因此:
- length 将会返回 2
- charAt 将返回不可打印的字符,因为这两个码位都是代理位,无法被打印出来
- charCodeAt 返回的是字符串中的码位的信息
- codePointAt 遇到代理对的时候,会将后续的代理对全部打印出来,因此可以完整的打印出一个字符代表的值
- 当 codePointAt 遇到的不是代理对,打印的值与 charCodeAt 是一样的
检测一个字符占用的编码单元数量
function isBit32(str) {
return str.codePointAt(0) > 0xFFFF;
}
console.log(isBit32('😁')); // true
console.log(isBit32('a')); // false
逻辑很直接,通过这种专门获取代理对的 API 很容易知道字符是否是代理对来表示的
根据码位生成字符
我们上面的字符 😁 的码位是 128513,我们可以根据这个码位,再反向生成这个字符
console.log(String.fromCodePoint(128513)); // 😁
总之就是,我们使用 CodePoint 而不是 charCode 就对了
其它字符串变更 API
子串识别
我们经常想要在一个字符串里查询另一个字符串是否存在,ES 6 中有 3 个 API
includes()startsWith()endsWith()
这三个方法的作用看名字就知道是啥意思,就不再细说了。
以上三个方法都接收 2 个参数:
- 第一个参数指定要搜索的字符串文本
- 第二个参数是可选的,指定一个搜索的位置的索引值。如果指定了这个位置,includes 和 startsWith 都会从这个位置开始搜索。而 endsWith 则从字符串长度减去这个索引值的位置开始搜索(也就是匹配倒数的位置是不是这个字符串)。
repeat() 方法
将一个字符串重复多次。
console.log('xa'.repeat(5));
输出:
(py3.5) czp@:~/workspace/knowledge-base/demos/node_start$ node hello.js
xaxaxaxaxa
模板字面量
模板字面量是允许嵌入表达式的字符串字面量。你可以使用多行字符串和字符串插值功能。
简而言之,模板字面量最后也会生成一个字符串。
基础语法
最简单是就是把模板字面量当成一个字符串使用
console.log(`aaaaaaa`);
注意我们这里的字符串,不是使用单引号,而是使用的反引号给包起来的,这就代表了一个模板字面量
模板字面量中不需要转义单引号和双引号,但是如果想输出反引号的话,需要转义,也就是
console.log(`\`aaaaaa\``);
多行字符串
let line = `line1
line2`
console.log(line);
console.log(line.length);
将会输出
(py3.5) czp@:~/workspace/knowledge-base/demos/node_start$ node hello.js
line1
line2
11
原理:被反引号包围的空白字符(空格、换行)都输于字符串的一部分
我们使用这个特性来创建多行字符串的时候,要小心缩进,例如:
let line = `line1
line2`
console.log(line);
console.log(line.length);
将会输出
(py3.5) czp@:~/workspace/knowledge-base/demos/node_start$ node hello.js
line1
line2
23
最佳实践
let line = `
<div>
xxx
</div>`
console.log(line.trim());
console.log(line.length);
输出
(py3.5) czp@:~/workspace/knowledge-base/demos/node_start$ node hello.js
<div>
xxx
</div>
21
我们通过第一行留白,然后 trim,来保持缩进
字符串占位符
模板字面量支持占位符的特性,也就是说,在模板字面量中,留下一些可供替换的地方,然后使用变量或者表达式对字面量进行替换
let name = "张三";
let message = `你好,${name}`;
console.log(message);
将会输出:你好,张三
这个功能其实也是很多语言都有的一种特性了。scala 里也有这种模板替换的功能。
注:模板字面量可以访问作用域中的所有可访问的变量,嵌入未定义的变量将会抛出错误
嵌入表达式
let count = 10;
let price = 0.25;
let message = `${count} items cost $${(count * price).toFixed(2)}`;
console.log(message); // 10 items cost $2.50
占位符里可以嵌入任何合法的 JavaScript 表达式,所以我们可以在里面进行函数调用和其它的计算
嵌入模板字面量
由于占位符可以嵌入合法的表达式,因此模板字面量本身也是可以被嵌入进去的
let name = "张三";
let message = `你好,${
`我的名字是${name}`
}`;
console.log(message); // 你好,我的名字是张三
很明显:先计算里面的表达式,再计算外面的表达式
标签模板
标签模板就是加强版本的模板占位符
或者可以这么说,模板字面量其实就是通过标签模板来完成的,下面看例子
let age = 22;
let name = 'czp'
var tag = function (arr, age, name) {
console.log(arr);
console.log(age);
console.log(name);
return 'hehehe';
}
var message = tag`my age is ${age}, my name is ${name}`;
console.log(message);
将会输出
(py3.5) czp@:~/workspace/knowledge-base/demos/node_start$ node hello.js
[ 'my age is ', ', my name is ', '' ]
22
czp
hehehe
分析:
var message = tag`my age is ${age}, my name is ${name}`;
这一行跟之前的模板字面量的区别就是前面加了一个标签:tag
实际上这个标签就是我们自己定义的一个函数的名称(名称可以自己随便定义)
这一行的实际意义是:
将模板字面量进行分割,例如
my age is ${age}, my name is ${name}将会分割成:- "my age is "
- ", my name is "
- ""
这个分割是按照里面的嵌入模板进行分割,任何一个类似于
${name}这样的模板都会将字符串分割成左边和右边两部分(右边没有字符的时候就会是一个 "" 空字符串)将分割后的字符串数组
["my age is " , ", my name is " , ""]作为tag函数的第一个参数第二个参数是
${age}的值,第三个参数是${name}的值,总而言之后续参数是模板按照顺序解析的值tag函数的返回值将会是这个模板的值,也就是说tag`my age is ${age}, my name is ${name}`; 等价于 tag(["my age is " , ", my name is " , ""], age, name);
显然,我们可以利用标签模板实现占位符的功能,也可以利用它实现一些更高级的功能,总之算是 ES 6 提供的一种模板 API 吧