ES6—4、6

字符串的扩展

1.字符的 Unicode 表示法

允许采用\uxxxx的形式表示一个字符,xxxx表示Unicode码点,超出\u0000~\uFFFF之间的字符必须用两个双字节形式表示。但是如果将码点放入大括号,就可正确解读字符。

"\uD842\uDFB7"
// "𠮷"

"\u20BB7"
// " 7"

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

JavaScript表示一个字符:

'\z' === 'z'  // true
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
'\u{7A}' === 'z' // true
2.字符串的遍历器接口

字符串可以被for…of循环遍历,且可以识别大于0xFFFF的码点。

for (let codePoint of 'foo') {
  console.log(codePoint)
}
// "f"
// "o"
// "o"

let text = String.fromCodePoint(0x20BB7);
for (let i = 0; i < text.length; i++) {
  console.log(text[i]);
}
// " "
// " "

for (let i of text) {
  console.log(i);
}
// "𠮷"

text只有一个字符,但是for循环会认为它包含两个字符(都不可打印),而for…of循环可以正确识别出这个字符。

3.直接输入U+2028和U+2029

JavaScript 字符串允许直接输入字符,以及输入字符的转义形式。但是,JavaScript 规定有5个字符,不能在字符串里面直接使用,只能使用转义形式。
U+005C:反斜杠
U+000D:回车
U+2028:行分隔符
U+2029:段分隔符
U+000A:换行符
JSON 格式允许字符串里面直接使用 U+2028(行分隔符)和 U+2029(段分隔符)
ES2019 允许 JavaScript 字符串直接输入 U+2028(行分隔符)和 U+2029(段分隔符)。

4.JSON.stringify() 的改造

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

JSON.stringify('\u{D834}') // ""\\uD834""
JSON.stringify('\uDF06\uD834') // ""\\udf06\\ud834""
5.模板字符串

模板字符串是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量,模板字符串中嵌入变量,需要将变量名写在${}之中。如果在模板字符串中需要使用反引号,则前面要用反斜杠转义,所有的空格和缩进都会被保留在输出之中。如果你不想要换行,可以使用trim方法消除它。

$('#list').html(`
<ul>
  <li>first</li>
  <li>second</li>
</ul>
`.trim());

如果大括号中的值不是字符串,将按照一般的规则转为字符串。比如,大括号中是一个对象,将默认调用对象的toString方法。

模板字符串的嵌套:

const tmpl = addrs => `
  <table>
  ${addrs.map(addr => `
    <tr><td>${addr.first}</td></tr>
    <tr><td>${addr.last}</td></tr>
  `).join('')}
  </table>
`;
const data = [
    { first: '<Jane>', last: 'Bond' },
    { first: 'Lars', last: '<Croft>' },
];

console.log(tmpl(data));
// <table>
//
//   <tr><td><Jane></td></tr>
//   <tr><td>Bond</td></tr>
//
//   <tr><td>Lars</td></tr>
//   <tr><td><Croft></td></tr>
//
// </table>
6.标签模板

标签模板其实不是模板,而是函数调用的一种特殊形式。“标签”指的就是函数,紧跟在后面的模板字符串就是它的参数。但是,如果模板字符里面有变量,就不是简单的调用了,而是会将模板字符串先处理成多个参数,再调用函数。

let a = 5;
let b = 10;

tag`Hello ${ a + b } world ${ a * b }`;
// 等同于
tag(['Hello ', ' world ', ''], 15, 50);
let a = 5;
let b = 10;

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

  return "OK";
}

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

一个复杂的例子

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;
}

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

“标签模板”的一个重要应用,就是过滤 HTML 字符串,防止用户输入恶意内容。
标签模板的另一个应用,就是多语言转换(国际化处理)。
在 JavaScript 语言之中嵌入其他语言。
模板处理函数的第一个参数(模板字符串数组),还有一个raw属性,保存的是转义后的原字符串。

tag`First line\nSecond line`

function tag(strings) {
  console.log(strings.raw[0]);
  // strings.raw[0] 为 "First line\\nSecond line"
  // 打印输出 "First line\nSecond line"
}

正则的扩展

1.RegExp 构造函数

如果RegExp构造函数第一个参数是一个正则对象,那么可以使用第二个参数指定修饰符。而且,返回的正则表达式会忽略原有的正则表达式的修饰符,只使用新指定的修饰符。

new RegExp(/abc/ig, 'i').flags
// "i"
//原有正则对象的修饰符是ig,它会被第二个参数i覆盖
2.字符串的正则方法

字符串对象共有 4 个方法,可以使用正则表达式:match()、replace()、search()和split()。

3.u修饰符

ES6 对正则表达式添加了u修饰符,含义为“Unicode 模式”,用来正确处理大于\uFFFF的 Unicode 字符。也就是说,会正确处理四个字节的 UTF-16 编码。
(1)点字符
(2)Unicode 字符表示法
(3)量词
(4)预定义模式
(5)i修饰符
(6)转义

4.Unicode 字符表示法

正则实例对象新增unicode属性,表示是否设置了u修饰符

const r1 = /hello/;
const r2 = /hello/u;

r1.unicode // false
r2.unicode // true
5.y修饰符

y修饰符的作用与g修饰符类似,也是全局匹配,后一次匹配都从上一次匹配成功的下一个位置开始。不同之处在于,g修饰符只要剩余位置中存在匹配就可,而y修饰符确保匹配必须从剩余的第一个位置开始,这也就是“粘连”的涵义。

var s = 'aaa_aa_a';
var r1 = /a+/g;
var r2 = /a+/y;

r1.exec(s) // ["aaa"]
r2.exec(s) // ["aaa"]

r1.exec(s) // ["aa"]
r2.exec(s) // null

y修饰符的一个应用,是从字符串提取 token(词元),y修饰符确保了匹配之间不会有漏掉的字符

const TOKEN_Y = /\s*(\+|[0-9]+)\s*/y;
const TOKEN_G  = /\s*(\+|[0-9]+)\s*/g;

tokenize(TOKEN_Y, '3 + 4')
// [ '3', '+', '4' ]
tokenize(TOKEN_G, '3 + 4')
// [ '3', '+', '4' ]

function tokenize(TOKEN_REGEX, str) {
  let result = [];
  let match;
  while (match = TOKEN_REGEX.exec(str)) {
    result.push(match[1]);
  }
  return result;
}
6.RegExp.prototype.sticky 属性

表示是否设置了y修饰符

var r = /hello\d/y;
r.sticky // true
7.RegExp.prototype.flags 属性

返回正则表达式的修饰符

// ES5 的 source 属性
// 返回正则表达式的正文
/abc/ig.source
// "abc"

// ES6 的 flags 属性
// 返回正则表达式的修饰符
/abc/ig.flags
// 'gi'
8.s 修饰符:dotAll 模式

点(.)是一个特殊字符,代表任意的单个字符,但是有两个例外。一个是四个字节的 UTF-16 字符,这个可以用u修饰符解决;另一个是行终止符。
换行符(\n)
回车符(\r)
U+2028 行分隔符
U+2029 段分隔符

/foo.bar/.test('foo\nbar')
// false
//因为点不能匹配\n

引入s修饰符,使得.可以匹配任意单个字符,点(dot)代表一切字符。所以,正则表达式还引入了一个dotAll属性,返回一个布尔值,表示该正则表达式是否处在dotAll模式。

const re = /foo.bar/s;
// 另一种写法
// const re = new RegExp('foo.bar', 's');
re.test('foo\nbar') // true
re.dotAll // true
re.flags // 's'
9.后行断言

x只有在y后面才匹配,必须写成/(?<=y)x/。比如,只匹配美元符号之后的数字,要写成/(?<=$)\d+/。“后行否定断言”则与“先行否定断言”相反,x只有不在y后面才匹配,必须写成/(?<!y)x/。比如,只匹配不在美元符号后面的数字,要写成/(?<!$)\d+/。

/(?<=\$)\d+/.exec('Benjamin Franklin is on the $100 bill')  // ["100"]
/(?<!\$)\d+/.exec('it’s is worth about €90')                // ["90"]

“先右后左”与所有其他正则操作相反,导致了一些不符合预期的行为
(1)后行断言的组匹配,与正常情况下结果是不一样的
(2)反斜杠引用,也与通常的顺序相反,必须放在对应的那个括号之前

/(?<=(\d+)(\d+))$/.exec('1053') // ["", "1", "053"]
/^(\d+)(\d+)$/.exec('1053') // ["1053", "105", "3"]

/(?<=(o)d\1)r/.exec('hodor')  // null
/(?<=\1d(o))r/.exec('hodor')  // ["r", "o"]
10.Unicode 属性类

\p{…}和\P{…},允许正则表达式匹配符合 Unicode 某种属性的所有字符,使用的时候一定要加上u修饰符。
Unicode 属性类要指定属性名和属性值

\p{UnicodePropertyName=UnicodePropertyValue}

const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π') // true

\P{…}是\p{…}的反向匹配,即匹配不满足条件的字符

11.具名组匹配

“具名组匹配”在圆括号内部,模式的头部添加“问号 + 尖括号 + 组名”(?< year>),然后就可以在exec方法返回结果的groups属性上引用该组名。同时,数字序号(matchObj[1])依然有效。

const RE_DATE = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;

const matchObj = RE_DATE.exec('1999-12-31');
const year = matchObj.groups.year; // 1999
const month = matchObj.groups.month; // 12
const day = matchObj.groups.day; // 31

如果具名组没有匹配,那么对应的groups对象属性会是undefined,但键名在groups是始终存在的。
(1)解构赋值和替换
有了具名组匹配以后,可以使用解构赋值直接从匹配结果上为变量赋值

let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
one  // foo
two  // bar

字符串替换时,使用$<组名>引用具名组

let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;

'2015-01-02'.replace(re, '$<day>/$<month>/$<year>')
// '02/01/2015'

(2)引用
如果要在正则表达式内部引用某个“具名组匹配”,可以使用\k<组名>的写法
数字引用(\1)依然有效

const RE_TWICE = /^(?<word>[a-z]+)!\k<word>!\1$/;
RE_TWICE.test('abc!abc!abc') // true
RE_TWICE.test('abc!abc!ab') // false
12.正则匹配索引

exec()方法的返回结果加上indices属性,在这个属性上面可以拿到匹配的开始位置和结束位置

const text = 'zabbcdef';
const re = /ab/;
const result = re.exec(text);

result.index // 1
result.indices // [ [1, 3] ]

开始位置包含在匹配结果之中,但是结束位置不包含在匹配结果之中

多组匹配:

const text = 'zabbcdef';
const re = /ab+(cd(ef))/;
const result = re.exec(text);

result.indices // [ [1, 8], [4, 8], [6, 8] ]

如果正则表达式包含具名组匹配,indices属性数组还会有一个groups属性

const text = 'zabbcdef';
const re = /ab+(?<Z>cd)/;
const result = re.exec(text);

result.indices.groups // { Z: [ 4, 6 ] }

如果获取组匹配不成功,indices属性数组的对应成员则为undefined,indices.groups属性对象的对应成员也是undefined。

13.String.prototype.matchAll()

如果一个正则表达式在字符串里面有多个匹配,现在一般使用g修饰符或y修饰符,在循环里面逐一取出,String.prototype.matchAll()方法,可以一次性取出所有匹配。不过,它返回的是一个遍历器,而不是数组。

const string = 'test1test2test3';
const regex = /t(e)(st(\d?))/g;

for (const match of string.matchAll(regex)) {
  console.log(match);
}
// ["test1", "e", "st1", "1", index: 0, input: "test1test2test3"]
// ["test2", "e", "st2", "2", index: 5, input: "test1test2test3"]
// ["test3", "e", "st3", "3", index: 10, input: "test1test2test3"]

遍历器转为数组:
(1)使用…运算符
(2)Array.from()方法

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值