字符串与正则表达式

深入理解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 最重要的部分。标签是接收模板字面量片段作为参数的函数,你可以使 用它们来返回合适的字符串。这些数据包括了字面量、等价的原始值以及替换位的值,标签 使用这些信息片段来决定输出。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值