字符串与正则表达式

深入理解ES6之字符创与正则表达式

一、更好的 Unicode 支持

在ES6之前,JS的字符串以16位字符编码(UCS-2)为基础,每个16位序列都是一个码元(code unit),用于表示一个字符。
Unicode 的明确目标是给世界上所有的字符提供全局唯一表示符

二、codePointAt()方法

codePointAt() 可以在给定字符串中按位置提取Unicode代码点。该方法接受的是码元位置而非字符位置,并返回一个整数值,以下示例是《深入理解ES6》的例子,与本人在自己的机器上测试的结果不一致,具体原因不明,如果有人清楚,还请不吝赐教<__ _ __>

var text = "a";

console.log(text.charCodeAt(0)); // 55362
console.log(text.charCodeAt(1)); // 57271
console.log(text.charCodeAt(2)); // 97

console.log(text.codePointAt(0)); // 134071
console.log(text.codePointAt(1)); // 57271
console.log(text.codePointAt(2)); // 97

codePointAt() 方法的返回值一般与 charCodeAt() 相同,除非操作对象并不是BMP(基本多语言平面,即0x0000-0xFFFF,包含了世界上最常用的字符)字符,在上例中,text字符串的第一个字符不是BMP字符。
判断字符包含了一个还是两个码元,对该字符调用codePointAt() 方法就是最简单的方法,示例如下:

function is32Bit(c){
    return c.codePointAt(0) > 0xFFFF;
}
console.log(is32Bit("")) // true
console.log(is32Bit("a")) // false

这是本人测试的结果

var text = "a";

console.log(text.charCodeAt(0)); // 97
console.log(text.charCodeAt(1)); // NaN
console.log(text.charCodeAt(2)); // NaN

console.log(text.codePointAt(0)); // 97
console.log(text.codePointAt(1)); // undefined
console.log(text.codePointAt(2)); // undefined

function is32Bit(c){
    return c.codePointAt(0) > 0xFFFF;
}
console.log(is32Bit("")) // false
console.log(is32Bit("a")) // false

String.fromCodePoint() 方法

借助 String.fromCodePoint() 用给定的代码点来产生包含单个字符的字符串,示例如下:

console.log(String.fromCodePoint(97)); //a

可以将 String.fromCodePoint() 视为 String.fromCharCode() 的完善版本,两者处理BMP字符时会返回相同结果,只有处理BMP范围之外的字符时才会有差异。

三、normalize() 方法

Unicode 另一个有趣之处是,不同的字符在排序或其它一些比较操作中可能会被认为是相同的。有两种方式可以定义这种关联性:第一种规范相等性(canonical equivalence),意味着两个代码点序列在所有方面都被认为是可互换的。例如,两个字符的组合可以规范等同于另一个字符第二种关联性是兼容性(compatibility),两个兼容的代码点序列看起来有差别,但在特定条件下可互换使用。
由于这些关联性,文本内容在根本上相同的两个字符串就可以包含不同的代码点序列。
normalize() 方法支持 Unicode 标准形式,该方法接受单个可选的字符串参数,用于指示需要使用系列哪种 Unicode 标准形式:

  • Normalization From Canonical Composition(“NFC”),这是默认值;
  • Normalization From Canonical Decompositon(“NFD”);
  • Normalization From Compatibility Composition(“NFKC”);
  • Normalization From Compatibility Decomposition(“NFKD”).

只需记住,当比较字符串时,它们必须被标准化为同一种形式

四、正则表达式 u 标志

正则表达式假定单个字符使用一个16位的码元来表示,***ES6***为正则表达式定义了用于处理 Unicode 的 u 标志。
当一个正则表达式设置了 u 标志时,它的工作模式将切换到针对字符,而不是针对码元。这意味着正则表达式将不会被字符串中的代理对所混淆,而是会如预期那样工作。
借助 u 标志,可以使用正则表达式判断一个字符串包含多少个代码点,示例如下:

function codePointLength(text) {
    var result = text.match(/[/s/S]/gu); // 检查text中的空白字符与非空白字符(使用 [\s\S] 以确保该模式能匹配换行符)
    return result ? result.length : 0;
}
console.log(codePointLength("abc")); // 3
console.log(codePointLength("bc")); // 2

判断是否支持 u 标志
使用一个函数来判断是否支持 u 标志是最安全的方式,示例如下:

function hasRegExpU(){
    try {
        var pattern = new RegExp(".","u");
        return true;
    }catch(ex){
        return false;
    }
}

五、识别子字符串的方法

  • includes() 方法,在给定文本存在于字符串中的任意位置时会返回 true ,否则返回 false;

  • startsWith() 方法,在给定文本出现在字符串起始处时返回 true ,否则返回 false ;

  • endsWith() 方法,在给定文本出现在字符串结尾处时返回 true ,否则返回 false 。
    以上方法都接受两个参数:需要搜索的文本,以及可选的搜索起始位置索引。示例如下:

    var msg = "Hello world!";
    console.log(msg.startsWith("Hello")); //true
    console.log(msg.endsWith("!")); //true
    console.log(msg.includes("o"));//true
    
    console.log(msg.startsWith("o"));// false
    console.log(msg.endsWith("world!"));// true
    console.log(msg.includes("x"));// false
    
    console.log(msg.startsWith("o",4));// true
    console.log(msg.endsWith("o",8)); // true
    console.log(msg.includes("o",8)); // false
    
  • repeat() 方法
    repeat() 方法接受一个参数作为字符串的重复次数,返回一个将初始字符串重复指定次数的新字符串,示例如下:

    console.log("x".repeat(3)); // "xxx"
    console.log("hello".repeat(2)); // "hellohello"
    console.log("abc".repeat(2)); // "abcabc"
    

    此方法在操纵文本时特别有用,尤其是在需要产生缩进的代码格斯化工具中,示例如下:

    // indent 使用了一定数量的空格
    var indent = " ".repeat(4),indentLevel = 0;
    
    // 每当你增加缩进
    var newIndent = indent.repeat(++indentLevel);
    

    第一次调用 repeat() 创建了一个包含四个空格的字符串,而 indentLevel 变量会持续追踪 缩进的级别。此后,你可以仅通过增加 indentLevel 的值来调用 repeat() 方法,便可以改变 空格数量。

六、正则表达式 y 标志

y 标志影响正则表达式搜索时的粘连(sticky)属性,它表示从正则表达式的 lastIndex 属性值的位置开始检索字符串中的匹配字符。如果在该位置没有匹配成功,那么正则表达式将停止检索。
皮一下_:正则表达式非常非常重要,大家一定要熟练掌握。

七、复制正则表达式

var re1 = /ab/i,
    re2 = new RegExp(re1);

re2 变量知识 re1 的一个副本。但如果你向 RegExp 构造器传递了第二个参数,那么在 ES5 改代码就无法工作,ES6 修改了这个行为,允许使用第二个参数,并且让它覆盖第一个参数中的标志,示例如下:

var re1 = /ab/i,

    // ES5 中会抛出错误,ES6 中可用
    re2 = new RegExp(re1,"g");

console.log(re1.toString()); // "/ab/i"
console.log(re2.toString()); // "/ab/g"

console.log(re1.test("ab")) // true
console.log(re2.test("ab")) // true

console.log(re1.test("AB")) // true
console.log(re2.test("AB")) // false

八、flags 属性

flags 属性用于配合 source (ES5用来获取正则表达式的文本)属性,让标志的获取变得更容易。这两个属性均为只有 getter 的原型访问器属性,因此都是只读的。 flags 属性使得检查正则表达式更容易,有助于测试与继承。示例如下:

var re = /ab/g;
console.log(re.source); // "ab"
console.log(re.flags); // "g"

九、模板字面量

ES6 的模板字面量(template literal)提高了创建领域专用语言(domain-specific language,DSL 是被设计用于特定有限目的的编程语言,与通用目的语言如 JavaScript 相反)的语法,与ES5及之前版本的解决方案相比,处理内容可以更安全。ECMAScript wiki 在 template literal strawman 上提供了如下描述:

本方案通过语法糖扩展了 ECMAScript 的语法,允许语言库提供 DSL 以便制作、查询并 操纵来自于其它语言的内容,并且对注入攻击( 如 XSS 、 SQL 注入,等等 )能够免疫 或具有抗性。

不过实际上,模板字面量是 ES6 针对 JS 直到 ES5 依然完全缺失的如下功能的回应:

  • 多行字符串:针对多行字符串的形式概念;
  • 基本的字符串格式化:将字符串部分替换为已存在的变量值的能力;
  • HTML 转义:能转换字符串以便将其安全插入到HTML中的能力。

模板字面量以一种新的方式解决了这些问题,而并未给 JS 已有的字符串添加额外功能

9.1 基本语法

模板字面量的最简单语法,是使用反引号(`)来包裹普通字符串,示例如下,此示例说明了 message 变量包含的是一个普通的字符串。若想在字符串中包含反引号,只需使用反斜杠(\) 转义即可。

let message = `Hello world!`;

console.log(message);
console.log(typeof message);
console.log(message.length);

//使用反斜杠转义 (\`)
let message2 = `\`Hello\` world!`;
console.log(message2); // "`Hello` world!"
console.log(typeof message2); // "string"
console.log(message2.length); // 14

在模板字面量中无需对双引号或单引号进行转义

9.2 多行字符串

ES6 的模板字面量使多行字符串更易创建,只需在想要的位置包含换行即可,而且它会显示在结果中,示例如下:

let message = `Multiline
string`;
console.log(message);   // "Multiline
                        // string"
console.log(message.length); // 16

如果让多行文本保持合适的缩进对你来说很重要,请考虑将多行模板字面量的第一行空置并在此后进行缩进,示例如下,

let html = `
<div>
    <h1>Title</h1>
</div>`.trim(); // trim()方法用于删除字符串的头尾空格,它不会改变原始字符串。

如果你喜欢的话,也可以早模板字面量中使用 \n 来指示换行的插入位置:

let message = `Multiline\nstring`;
console.log(message); // "Multiline
                      // string" 
console.log(message.length); // 16
9.4 制造替换符

模板字面量和普通 JS 字符串的真正区别在于前者的“替换位”。替换位允许你将任何有效的 JS 表达式嵌入到模板字面量中,并将其结果输出为字符串的一部分。
替换位由起始的 " ${ " 与结束的 " } " 来界定,之间允许放入任意的 JS 表达式。示例如下:

let name = "Nicholas",
    message = `Hello, ${name}.`;
console.log(message); // "Hello, Nicholas."

替换位 ${name} 会访问本地变量 name ,并将其插入到 message 字符串中。message 变量会立即保留该替换位的结果。

模板字面量能访问到作用域中任意的可访问变量。试图使用未定义的变量会抛出错误,无论是严格模式还是非严格模式。
替换位可替换的不仅仅是简单的变量名,你可以轻易嵌入计算、函数调用,等等。示例如下:

let count  = 10,
    price = 0.25,
    message = `${count} items cost $${(count * price).toFixed(2)}.`; //.toFixed(2) 方法将结果格式化为两位小数
console.log(message);

十、标签化模板

上节学习的模板字面量真正的力量来源于标签化模板。一个模板标签(template tag)能对模板字面量进行转换并返回最终的字符串值,标签在模板的起始处被指定,即在第一个反引号(`) 之前,示例如下:

let message = tag`Hello world`;// tag会被应用到 `Hello world` 模板字面量上的模板标签
10.1 定义标签

一个标签(tag)仅是一个函数,它被调用时接收需要处理的模板字面量数据。标签所接收的数据被划分为独立片段,并且必须将它们组合起来以创建结果。第一个参数是个数组,包含被 JS 解释过的字面量字符串,随后的参数是每个替换位的解释值
标签函数的参数一般定义为剩余参数形式,以便更容易处理数据,示例如下:

function tag(literals, ...subscriptions) {
    // 返回一个字符串
}

为了更好地理解传递给标签的是什么参数,可研究下例:

let count = 10,
    price = 0.25,
    message = passthru`${count} items cost $${(count * price).toFixed(2)}.`;

分析上述实例。如果你拥有一个名为 passthru() 的函数,该函数将会接收三个参数。首先是一个 literals 数组,包含如下元素:

  • 在首个替换位之前的空字符串("");
  • 首个替换位与第二个替换位之间的字符串(" items cost $");
  • 第二个替换位之后的字符串(".")。
    接下来的参数会是 10 ,也就是 count 变量的解释值,它也会成为 substitutions 的数组的第一个元素。最后一个参数则会是 " 2.50 ",即 (count * price).toFixed(2) 的解释值,并且会是 substitutions 数组的第二个元素。
    需要注意 literals 的第一个元素是空字符串,以确保 literals[0] 总是字符串的起始部分正如 literals[literals.length - 1] 总是字符串的结尾部分。同时替换位的元素数量也总是比字面量元素少 1,。
    可以交替使用 literals 与 substitutions 数组来创建一个结果字符串,示例如下:
function passthru(literals, ...subscriptions) {
    let result = "";
    
    // 仅使用substitutions 的元素数量来进行循环
    for (let i = 0; i < subscriptions.length; i++) {
        result += literals[i];
        result += subscriptions[i];
    }
    result += literals[literals.length - 1];
    return result;
}
let count = 10,
    price = 0.25,
    message = passthru`${count} items cost $${(count * price).toFixed(2)}.`;
console.log(message); // "10 items cost $2.50."
10.2 使用模板字面量中的原始值

模板标签也能访问字符串的原始信息,主要指的是可以访问字符在转义之前的形式。获取原始字符串值的最简单方式是使用内置的 String.raw() 标签,示例如下:

let message1 = `Multiline\nstring`,
    message2 = String.raw`Multiline\nstring`;
console.log(message1); // "Multiline
                        // string"
console.log(message2); // "Multiline\\nstring"

十一、总结

完整的 Unicode 支持允许 JS 以合理的方式处理 UTF-16 字符。通过 codePointAt() 与 String.fromCodePoint() 在代码点和字符之间转换的能力,是字符串操作的一大进步。正则 表达式新增的 u 标志使得直接操作代码点而不是 16 位字符变为可能,而 normalize() 方 法则允许进行更恰当的字符串比较。
ES6 也添加了操作字符串的新方法,允许你更容易识别子字符串,而不用管它在父字符串中 的位置。正则表达式同样引入了许多功能。
模板字面量是 ES6 的一项重要补充,允许你创建领域专用语言( DSL )让字符串的创建更容 易。能将变量直接嵌入到模板字面量中,意味着开发者在组合长字符串与变量时,有了一种 比字符串拼接更为安全的工具。
内置的多行字符串支持,是普通 JS 字符串绝对无法做到的,这使得模板字面量成为凌驾于前 者之上的有用升级。尽管在模板字面量中允许直接使用换行,你依然可以使用 \n 或其它字 符转义序列。
模板标签是创建 DSL 最重要的部分。标签是接收模板字面量片段作为参数的函数,你可以使 用它们来返回合适的字符串。这些数据包括了字面量、等价的原始值以及替换位的值,标签 使用这些信息片段来决定输出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值