以前用到字符串的方法时候,并不会深刻的去思考其中的原理,所以在es6新增的这些方法里就有点蒙圈了,于是想要搞清楚为什么会新增这些方法,以及如何使用这些方法。
在博客园上看见一篇大神SamWeb的总结,很是详细,讲解透彻,故,引用于此,望莫失莫忘。
地址: https://www.cnblogs.com/SamWeb/p/7091469.html
ES6字符串操作
讨论字符串操作之前,我们先来了解一下Unicode 编码的由来,因为Js中的字符串就是一系列Unicode码的集合。
我们都知道,世界上存在着各种各样的语言,汉语,英语,日语等,相对应的,也就存在各种各样的字符,如汉语就是对应我们博大精深的汉字,英语则对应着英文字符,各种不同的字符存在,也导致各国制定各自不同的标准,来使用字符存储在计算机上,如Ascii 码表,GBK表,这就导致了一个问题,各国之间的文件不能够交换使用,容易出现乱码问题。这时国际标准化组织就想把各国的字符都统一起来,把它们放到一张码表中,然后在这张表中,给每一个字符都分配一个数字,成为这个字符的唯一的标识符,由于每个字符都对应唯一的一个数字,所以不会出现冲突。这张表就是Unicode码表或Unicode编码字符集,这个数字又叫做码点(Code Point). 码表的样子如下
通过这张表,我们可以看到字符和数字的一一对应关系。 丽 ---> 数字4E3D, 充 -----> 数字5145, 注意这里的数字都是用的16进制。
想法是美好的,但是怎么落实到计算机中呢?怎么在计算中表示这些码点呢? 因为计算机中全是二进制,我们要用多少个字节来表示一个数字,这时出现了不同的实现方案,就是我们经常听说的UTF-8, UTF-16, UTF-32等, UTF-16 就是使用16个bit 位, 也就是两个字节来表示一个码点,这16个字节叫做代码单元(code unit),很明显这里有一个问题,两个字节的能够表示的最大数是65535, 如果码点超出了65535怎么处理? 比如码点134071, 那就用两个代码单元进行表示。
Js 中对字符串的操作就是基于以上理论进行实现的,你可能还记得charCodeAt方法,它就是获取的代码单元
<script>
let text = "𠮷";
console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)) // 57271
</script>
可以看到 𠮷 是有两个代码单元组成。Js中所有字符串操作都是基于代码单元的,如:length方法,它也是指多少个代码单元, 我们可以测试一下 𠮷 的长度,如果按代码单元的话,它的长度就是2.
let text = "𠮷";
console.log(text.length); // 2
ES6 为了增强对unicode 编码的支持,增加了一个方法codePointAt(),它直接返回的是字符的码点。这个方法接受一个参数(代码单元的位置),返回一个整数。
let text = "𠮷a";
console.log(text.codePointAt(0)); // 134071console.log(text.codePointAt(1)); // 57271console.log(text.codePointAt(2)); // 97
在原来代码单元0的位置上,我们获取到的是整个字符的码点值,同时我们如果要获取到a的码点值,只能是通过2来获取,因为𠮷 是有两个代码单元组成,a 的代码单元位置只能是第3位。在一个字符代码单元0的位置,我们始终会获取的它的码点值。
通过码点值,我们可以很容易的判断一个字符是2个字节表示,然后多余两个字符,只要它的码点值大于65535(16进制0xFFFF)就是多余2个字节
function more16bit (char) {
return char.codePointAt(0) > 0xFFFF;
}
console.log(more16bit('a')) // false
console.log(more16bit('𠮷')) // true
有了codePointAt() 方法,肯定还要有一个相反的方法,通过码点来返回字符,那就是fromCodePoint
console.log(String.fromCodePoint(134071)) //𠮷
为了配合code point 码点,正则表达式增加了一个u标志符,当有u标志后,正则表达式会按码点进行匹配,而不再使用代码单元。当有一个双字节的字符时,如果用单字符进行匹配,按照代码单元它就会出错,按照码点则不会(一个码点就是对应一个字符)。看一下下面这个例子:
let text = '𠮷';
// 正则表达式/^.$/ 表示单个字符
console.log(/^.$/.test(text)); //false
console.log(/^.$/u.test(text)); // true 加了u 标志
console.log(/^..$/.test(text)); // true 当我们用两个字符进行匹配成功
字符串操作,其他方面的增强
三个判断包含关系的API: includes(), startsWith() , endsWith() 通过字面意思可以知道 includes判断一个字符串是否包含另一个字符串,startsWith 一个字符串是 否以一个字符串开始,endsWith 一个字符串是否以一个字行串结束,所以它们都接受一个必须参数,要搜索的字符串,和一个可选参数,索引值,表示从字符串的第几个位置开始查找。和indexof ,lastIndexof 用法一样。写几个例子就非常熟悉了
let msg = "Hello world!";
console.log(msg.startsWith("Hello")); // true 以Hello开头,正确
console.log(msg.endsWith("!")); // true 以!结尾,正确
console.log(msg.includes("o")); // true 包含o ,正确
console.log(msg.startsWith("o", 4)); // true 从开始第四个位置开始计算,第四个位置正好是o
console.log(msg.endsWith("o", 8)); // true 从倒数第8个位置开始,o正好是倒数第8个,注意,当倒数时候是从1开始记数
console.log(msg.includes("o", 8)); // false 正数第8个是r,r后面没有o
repeat 方法,就是对一个字符串重复多次,它接受一个参数,重复的次数,返回一个新字符串。
let msg = "Hello";
console.log(msg.repeat(4)) //HelloHelloHelloHello
模版字符串:字符串中可以很简单地插入变量,像我们用handlebars写html模版一样,所以叫模版字符串,基本语法,就是用反引号``把字符串括起来。
首先,模版字符串本质上也是字符串,我们可以像平时一样正常使用它。
let message = `Hello world!`;
console.log(message); // "Hello world!"
console.log(typeof message); // "string"
console.log(message.length); // 12
这样正常方式使用模版字符串的好处是我们可以非常轻松地写出多行字行串。
let message = `Hello
world!`;
console.log(message);
所有在反引号中的空白都会被保留, 浏览器控制台做如下输出:
这就非常有利于我们写html模版了
let html = `
<div>
<h1>Title</h1>
</div>`;
刚才我们说过,可以在html模版中插入js表达式, 插入的方式是:用${}把js表达式包起来
let count = 10, price = 0.25;
let message = `${count} items cost $${(count * price).toFixed(2)}.`;
console.log(message); // "10 items cost $2.50."
然而模版字符串真正强大的地方在于标签模板,它的语法非常简单,就是在模板字符串开始的反撇号前附加一个额外的标签即可
let newMessage = tag`${count} items cost $${(count * price).toFixed(2)}.`;
有了tag标签,我们要做就是定义这个标签,从而使我们可以对模版字符串进行各种转换操作,返回一个我们真正想要的字符串。其实tag标签就是一个函数,我们把它放到模版字符串中前面,就是对模版字符串调用这个函数.在这里要注意,调用tag函数的时候,并不是把整个模版字符串当作参数传递给这个函数,而是js引擎会把模版字符串进行分解,然后把分解后的结果传递给tag 函数。
分割的方式,则是以${}作为分界进行分割。上面的模版字符串就会分割成 “ ” ,count, "item cost $", (count*price).toFixed(2), "."
前面的空字符串一定要注意,在第一个${}前面没有内容,所以是一个空字符串,这样可以保证,分割的字符串永远比 ${} 包 含的变量多1. 分割完成后,得到的字符串会再合并成一个数组[“”, “item cost $”, "."] ,然后作为第一个参数传递给tag 函数,剩下的变量count,(count*price).toFixed(2) 则会作为单独的参数,按照它们在模版字符串中出现的顺序依次传递tag函数,
所以我们的tag函数接受的参数应该是([“”, “item cost $”, "."] , count, (count*price).toFixed(2))
tag 函数的定义应该是 function tag(lliterals, value1, value2), ES6 增加了一个...rest 操作符,它会把所有的剩余函数参数都收集到一个数组中,所以tag 函数的定义还可以是这样 function tag(lliterals, ...substitutions)
function tag(literals, ...substitutions) {
let result = '';
// 数组substitutions的长度永运比literals数组的长度少1个
substitutions.forEach((item,i)=> (result += literals[i],result += item))
// 把literals数组最后一个再加到新生成的字符串中.
result += literals[literals.length -1];
return result;
}
let count = 10, price = 0.25;
let newMessage = tag`${count} items cost $${(count * price).toFixed(2)}.`;
console.log(newMessage); // "10 items cost $2.50."
利用解构赋值和...rest操作符也可以对字符串进行解析,这时,js会把字符串作为数组看待,从而可以得到任意的字符,从而代替了charAt, substr 等方法。
// es6 解析字符串的方式
var str = 'helo';
var [first, ...tail] = str;
console.log(first) // 'h'
console.log(tail) // ['e','l','o']