« 回到博客列表

每天一点ES6(5):字符串的扩展

Tags: es6, string

继续打脸

呵呵呵呵呵,现在这个系列已经正式沦落为连作者也不知道啥时候更的节奏了……

这次来讲讲字符串,ES6对String类扩展了一些函数,使得操作字符串更加的便捷,一些以往必须借助额外的 Javascript 代码才能实现的效果,现在有了现成的函数。

改进的 Unicode 表示法

JavaScript 允许使用\uxxxx的形式表示一个字符,但在 ES6 之前,单个码点仅支持\u0000\uFFFF,超出该范围的必须用双字节形式表示,否则会解析错误。ES6对这一点做出了改进,只要将码点放入大括号,就能正确解读该字符,不受限于4位。例如下面的写法就是合法的,能够被正确解析:

"\u{20BB7}"  // "𠮷"

codePointAt()

JavaScript 中,1个字符固定为2个字节,对于需要4个字节存储的字符会被认为是2个字符,length会判定为2,charAt()无法读取整个字符,charCodeAt()只能分别返回前后各两个字节的值。

ES6 提供了 codePointAt(),能够正确处理4个字节存储的字符,返回一个字符的码点。但需要注意的是,这并没有改变 JavaScript 将2个字节视为1个字符的事实,只是自动识别出了这是个4字节的字符并返回了正确的码点而已,对于单个4字节的字符来说,charPointAt(0)返回完整字符的十进制码点,charPointAt(1)返回这个字符的后2个字节的十进制码点,效果等同于charCodeAt(1)

为了处理这种“不合理”的索引,可以借助for...of循环,它会正确识别32位的UTF-16字符。

var s = '𠮷a';
for (let ch of s) {
  console.log(ch.codePointAt(0).toString(16));
}
// "20bb7"
// 不可打印

可以利用charPointAt()来判断一个字符是双字节的还是四字节的:

function is32Bit(c) {
  return c.codePointAt(0) > 0xFFFF;
}

is32Bit("𠮷") // true
is32Bit("a") // false

String.fromCodePoint()

作用和charPointAt()相反,从四字节码字得到一个完整字符,解决了 ES5 中fromCharCode()只能识别双字节的问题。

需要注意的是,fromCharPoint()定义在String对象上,而codePointAt()定义在字符串的实例对象上。

at()

ES5 提供了charAt()方法,用于提取字符串中指定位置的字符,但同样存在无法处理四字节字符的问题,ES6 中有一个提案给出了at()来处理该问题。

normalize()

Unicode提供了两种方式来表示重音和音标等符号,一种是将带重音符号的字符单独作为一个字符,设定一个码点,另一种是提供单独的重音符号的码点,和普通字符合成一个四字节的字符。两种方法在视觉和语义上都没有差别,但JavaScript并不能将它们识别为相同的内容。

ES6 提供了字符串实例的normalize(),用来将字符的不同表示方式统一为相同的形式,这个过程称为“Unicode正规化”。normalize()接受一个参数作为判定规则,可选的值有4个:

目前normalize()还不能识别中文(比如“囍”和“喜喜”),以及三个及以上字符的合成。

includes(), startsWith(), endsWith()

在 ES6 之前,要想确定一个字符串是否包含某个子串,只能用indexOf()。ES6 提供了三个新的方法来进行检索:includes()startsWith()endsWith(),各自的作用看名字就知道了。三个方法都支持第二个参数,表示开始搜索的索引位置。(endsWith()比较特别,它的第二个参数表示前n个字符是否以第一个参数结尾)

repeat()

ES6 提供了原生函数用于重复字符串,方法接受一个非负整数用于指定重复次数,不小于-1的负数和NaN会被视为0,正小数会被向下取整,Infinity是不可取的,会报错,字符串会先试图转换成数字。

padStart(), padEnd()

这其实是 ES7 计划中的一部分,如果一个字符串不够指定长度,则对其进行填充以补足长度。方法接受两个参数,第一个参数是目标长度,如果字符串长度达不到这里指定的值,则进行补齐,否则啥也不做直接返回原字符串。第二个参数适用于补齐的内容(默认为空格),内容的长度未必刚好,不够的会重复,超出的会截断

模板字符串

通常我们要用 JavaScript 输出一些 HTML 时,会这样写:

$("#result").append(
  "There are <b>" + basket.count + "</b> "
  + "items in your basket, "
  + "<em>" + basket.onSale + "</em> are on sale!"
);

倒不是说上面这种写法有什么问题,只是比较繁琐。ES6 带来了更方便的写法:

$("#result").append(`
  There are <b>${basket.count}</b> items
  in your basket, <em>${basket.onSale}</em>
  are on sale!
`);

ES6 中通过反引号`标记模板字符串,这是一种增强型的字符串,可以当做普通字符串一样使用,同时还支持定义多行字符串,或以${obj.prop}的形式在字符串中嵌入表达式,表达式中可以包含变量、函数调用等各种 JavaScript 语句,嵌入的内容最终会被转换为字符串。(PHP中也有类似的写法,注意区分:{$obj['prop']}

如果需要引用模板字符串本身,只需在其外部用引号包裹即可。

标签模板

模板字符串可以紧跟在一个函数名后面,这个函数会被调用来处理这个模板字符串。

var a = 5;
var b = 10;

tag`Hello ${ a + b } world ${ a * b }`;

上面代码中的tag其实是一个函数名,可以取任意有效的变量名,虽然它长得并不像个函数。整个表达式的返回值就是tag函数处理模板字符串后的返回值。

事实上,tag函数依次接收了多个参数,第一个参数是个数组,数组的内容是模板字符串中除了花括号表达式以外的内容。从第二个参数起就是花括号表达式计算的结果。换言之,上面代码实际上相当于:

tag(['Hello ', ' world ', ''], 15, 50)

这里tag函数的实现是自定义的,在调用之前我们需要先定义它的行为,通常我们使用一个更加语义化的函数名。

function hello(s, v1, v2) {
  console.log(s[0]);
  console.log(s[1]);
  console.log(s[2]);
  console.log(v1);
  console.log(v2);

  return "OK";
}

var a = 5, b = 10;

hello`Hello ${ a + b } world ${ a * b}`;
// "Hello "
// " world "
// ""
// 15
// 50
// "OK"

标签模板被设计用来对输入进行过滤和处理,例如:过滤用户的非法输入、多语言国际化等,虽然不及Mastache来得强大,但很多功能都可以通过标签进一步实现,标签模板还能很好的和React,甚至是Java等语言共用。

String.raw()

ES6 中新增的String类的原生方法,用于返回一个连转义符号本身也被转义的最“原始”的字符串。该方法常用于处理模板字符串。其具体实现为:

String.raw = function (strings, ...values) {
  var output = "";
  for (var index = 0; index < values.length; index++) {
    output += strings.raw[index] + values[index];
  }

  output += strings.raw[index]
  return output;
}

和标签模板非常类似,但这是一个更加“正常”的函数。String.raw()的第一个参数必须是一个具有raw属性的对象,其值为一个数组或类数组对象。

该系列的其他文章

上一篇:每天一点ES6(4):Babel

下一篇:每天一点ES6(6):正则的扩展