ECMAScript6-基础3-字符串扩展

字符的 Unicode 表示法

概念

ES6允许采用\uxxxx形式表示一个字符,其中xxxx表示字符的 Unicode码点,其中的\uxxxx取值范围是\u0000到\uFFFF之间的字符;

那如果超出这个范围限制后,则需要2个\uxxxx形式来表示,而到了ES6,为了更加简洁的标识,不采用2个\uxxxx的方式了,直接采用“{码点}”的形式,即:只要将码点放入大括号,就能正确解读该字符;

案例讲解/注意事项

"\u{20BB7}"

"\u{41}\u{42}\u{43}"

let hello = 123;

hell\u{6F}

'\u{1F680}' === '\uD83D\uDE80'这里的大括号表示法与四字节的 UTF-16 编码等价

字符串的遍历器接口

概念

ES6 为字符串添加了Iterator遍历器接口,这样字符串可以使用for...of循环来遍历字符串;

这样的好处,除了在解构赋值的时候可以赋值给数组外,最大的优点在于可以识别大于0xFFFF的码点,这是传统的for循环无法做到的效果;

案例讲解/注意事项

let text = String.fromCodePoint(0x20BB7);

for (let i = 0; i < text.length; i++) {

  console.log(text[i]);

}这里会执行2次循环,每次循环输出一个空格

for (let i of text) {

  console.log(i);

}这里就只输出一个字符

直接输入 U+2028 和 U+2029

概念

JS字符串允许直接输入字符,或者字符的转义形式,例如:var str = “中”;也可以是这种形式:var str = “\u4e2d”;

但是,有5个特殊字符,无法直接输入,只能使用转义形式,这5个特殊字符是:

U+005C:反斜杠(reverse solidus)

U+000D:回车(carriage return)

U+2028:行分隔符(line separator)

U+2029:段分隔符(paragraph separator)

U+000A:换行符(line feed)

但是,这里有个问题,就是JSON格式的字符串,这种格式的字符串是允许直接使用U+2028(行分隔符)和U+2029(段分隔符),但这会导致JSON.parse解析报错;

为了解决这个问题,ES6允许字符串直接输入U+2028(行分隔符)和 U+2029(段分隔符)

案例讲解/注意事项

const json = '"\u2028"';

JSON.parse(json); // 可能报错

const PS = eval("'\u2028'");

JSON.stringify() 的改造

概念

按照标准,JSON 数据必须是 UTF-8 编码,但JSON.stringify()方法有可能返回不符合 UTF-8 标准的字符串,这是因为UTF-8标准规定,0xD800到0xDFFF之间的码点,不能单独使用,必须配对使用,而JSON.stringify()的问题在于,它可能返回0xD800到0xDFFF之间的单个码点;

既然知道原因,为了确保返回的是合法的UTF-8字符,ES6改变了JSON.stringify()的行为,如果遇到0xD800到0xDFFF之间的单个码点,或者不存在的配对形式,它会返回转义字符串,留给应用决定下一步的处理。

模板字符串

概念

在JS中拼接HTML输出,是非常复杂的,都是通过单双引号、+来进行拼接字符串,一旦单双引号没有配对,就会报错,再加上HTML标签的属性都是引号包裹,既费时,又费力;

在ES6中引入了“模板字符串”的概念来解决这个问题,个人感觉跟JAVA用到的模板引擎一个思路,都是解析特定的标识符;

模板字符串是增强版的字符串,用反引号(`)标识;

案例讲解/注意事项

可以表示普通字符串

`In JavaScript '\n' is a line-feed.`

 

 

可以表示多行字符串

`In JavaScript this is

 not legal.`

 

 

可以在字符串中嵌入变量

let name = "Bob", time = "today";

`Hello ${name}, how are you ${time}?`

 

 

可以在模板字符串中使用反引号,则前面要用反斜杠转义

let greeting = `\`Yo\` World!`;

 

 

注意:如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出之中

$('#list').html(`

<ul>

  <li>first</li>

  <li>second</li>

</ul>

`);

 

 

模板字符串中嵌入变量,需要将变量名写在${}之中

 `User ${user.name} is not authorized to do ${action}.`);

 

 

大括号内部可以放入任意的JS表达式

`${x} + ${y} = ${x + y}`

`${x} + ${y * 2} = ${x + y * 2}`

`${obj.x + obj.y}`这里调用的是obj的x属性、y属性

 

 

模板字符串之中还能调用函数

`foo ${fn()} bar`

函数名叫做fn()

 

 

如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法;如果模板字符串中的变量没有声明,将报错;

 

 

模板字符串允许嵌套

const tmpl = addrs => `

  <table>

  ${addrs.map(addr => `

    <tr><td>${addr.first}</td></tr>

    <tr><td>${addr.last}</td></tr>

  `).join('')}

  </table>

`;

 

 

如果需要引用模板字符串本身,在需要时执行,可以写成函数,来达到简介引用的效果

let func = (name) => `Hello ${name}!`;

func('Jack')

标签模板

概念

模板字符串还可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串,这被称为标签模板功能;

从定义上来讲,标签模板是函数调用的一种特殊形式,所谓的“标签”指的是函数,而紧跟在后面的模板字符串就是它的参数;

那问题来了,模板字符串作为参数,该如何拆解呢,这里主要有2种情况:

①模板字符串就是一个纯字符串;

②模板字符串包含变量;

案例讲解/注意事项

模板字符串包含变量时,先把模板字符串拆分成多个参数,再去调用函数:

let a = 5;

let b = 10;

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

这里的模板字符串Hello ${ a + b } world ${ a * b }被拆分成3部分,分别是['Hello ', ' world ', '']、15、50,因此,传入tag方法的参数也就变成了3个,此刻tag方法为:

function tag(stringArr, value1, value2){

  ...

}

// 等同于

function tag(stringArr, ...values){

  ...

}

然后我们来分析下过程:

①tag函数的第一个参数是一个数组,该数组的成员就是模板字符串中那些没有被变量替换的部分,你可以把变量理解为关键字,然后模板字符串通过split来分割字符串,然后split方法返回一个数组,这样,数组变量之间就肯定隔着一个变量,然后依次轮退,例如:hello与world之前,world与空字符串之间;

②后续的可变参数,就是对应着的用于split分割的变量;

③最后tag的调用形式是:tag(['Hello ',' world ', ''], 15, 50)

④整个表达式的返回值,就是tag函数处理模板字符串后的返回值;

 

 

下面有2个经典案例:

let total = 30;

let msg = passthru`The total is ${total} (${total*1.05} with tax)`;

function passthru(literals) {

  let result = '';

  let i = 0;

  while (i < literals.length) {

    result += literals[i++];

    if (i < arguments.length) {

      result += arguments[i];

    }

  }

  return result;

}

// "The total is 30 (31.5 with tax)"

function passthru(literals, ...values) {

  let output = "";

  let index;

  for (index = 0; index < values.length; index++) {

    output += literals[index] + values[index];

  }

 

  output += literals[index]

  return output;

}

注意:

如果模板字符串是纯字符串,那模板字符串也会作为数组传入参数中,只不过该数组的length=1;

如果模板字符串包含变量,则先把模板字符串用变量分割成数组,然后传入参数中;

重点在于:数组有个raw属性,该raw属性指向的也是一个数组,且raw属性指向的数组保存了转义后的原字符串,raw属性数组与参数数组的区别在于:raw指向的数组会对字符进行转义;

console.log`123`

//这里的123,虽然是纯字符串,但其还是作为数组来传入log方法中,所以应该是这样的:["123", raw: Array[1]],raw属性也指向了一个数组,且该数组长度为1;

 

 

tag`First line\nSecond line`

function tag(strings) {

  console.log(strings.raw[0]);

  // strings.raw[0] 为 "First line\\nSecond line"

  // 打印输出 "First line\nSecond line"

}

tag函数的第一个参数strings是一个数组,该数组的raw属性也指向了一个数组,且该数组的成员与strings数组完全一致,

比如,strings数组是["First line\nSecond line"],那么strings.raw数组就是["First line\\nSecond line"]。两者唯一的区别,就是字符串里面的斜杠都被转义了。

比如,strings.raw 数组会将\n视为\\和n两个字符,而不是换行符。这是为了方便取得转义之前的原始模板而设计的。

扩展

①过滤HTML字符串,防止用户输入恶意内容:

let message =  SaferHTML`<p>${sender} has sent you a message.</p>`;

 

function SaferHTML(templateData) {

  let s = templateData[0];

  for (let i = 1; i < arguments.length; i++) {

    let arg = String(arguments[i]);

 

    s += arg.replace(/&/g, "&")

            .replace(/</g, "<")

            .replace(/>/g, ">");

 

   s += templateData[i];

  }

  return s;

}

这里的arguments代表的就是function对象的arguments属性,只不过该属性也是对象,然后arguments从1开始遍历,获取用户输入的内容,然后再用正则表达式对关键字符进行转义

②多语言转换(国际化处理)

i18n`Welcome to ${siteName}, you are visitor number ${visitorNumber}!`

这里可以通过i18n来获取地区信息,进而实现国际化效果

③可以自定义实现类似于Mustache模板库的if、for功能

let libraryHtml = hashTemplate`

  <ul>

    #for book in ${myBooks}

      <li><i>#{book.title}</i> by #{book.author}</li>

    #end

  </ul>

`;

④可以嵌入其他语言:

jsx`

  <div>

    <input

      ref='input'

      onChange='${this.handleChange}'

      defaultValue='${this.state.value}' />

      ${this.state.value}

   </div>

`

jsx函数,将一个 DOM 字符串转为 React 对象。你可以在 GitHub 找到jsx函数的具体实现;

模板字符串的限制

概念

可以使用标签模板来在JS中嵌入其他语言,但这里有个细节,就是其他语言的语法,可能与JS的规范发生冲突,特别是与JS的模板字符串的规范发生冲突;

为了解决这个问题,ES2018放松了对标签模板里面的字符串转义的限制,如果遇到不合法的字符串转义,就返回undefined,而不是像之前报错,且从raw属性上面可以得到原始字符串;

本来应该报错的地方,ES6由于放松了对字符串转义的限制,所以引擎无法转义字符时,则返回为undefined,但raw属性依然可以得到原始字符串,进而可以对原字符串进行处理。

注意:

这种放松管制的场景,仅仅发生在标签模板解析字符串时有效,其他场合依然会报错;

案例讲解/注意事项

function latex(strings) {

  // ...

}

 

let document = latex`

\newcommand{\fun}{\textbf{Fun!}}  // 正常工作

\newcommand{\unicode}{\textbf{Unicode!}} // 报错

\newcommand{\xerxes}{\textbf{King!}} // 报错

 

Breve over the h goes \u{h}ere // 报错

`

这里作者想在标签模板里面可以嵌入LaTEX语言,虽然这些语法符合LaTEX规范,但是不符合JS的模板字符串的规范,在对\unicode进行Unicode转义、对\xerxes按照十六进制字符串转义时抛出异常;

 

本来在LaTEX正常的语法,却在JS中按照特定编码进行了转义,且转义过程报错,到了ES2018放松了管制,对于这种无法转义的情况,统一返回undefined,而不是报错

 

 

 

 

再看一个例子:

function tag(strs) {

  strs[0] === undefined

  strs.raw[0] === "\\unicode and \\u{55}";

}

tag`\unicode and \u{55}`

这里的原始数据是\unicode and \u{55},当ES2018进行转义时,会因为\unicode按照Unicode转义,但是转义失败,把整个\unicode and \u{55}转义结果变成了undefined,所以,这才导致了strs[0] = undefined;

而raw只是对某个特定字符转义,所以strs.raw[0] = \\unicode and \\u{55}

 

 

 

let bad = `bad escape sequence: \unicode`; // 报错

只对标签模板发送管制,其他场景无效;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值