重读 ES6 — 字符串、数值、正则的扩展

从去年开始学习及使用 ES6 语法,后得益于项目的推动,到现在已较广泛的使用 ES6 写代码了。

我们知道,ES6 较之 ES5,新增特性非常之多,变化非常大,几乎像是两种不同语言。尽管现在 ES6 满大街了,但仍有许多有用的特性很少触及。另外,对于已使用的特性,有的使用频率高,有的使用频率比较低。

为了在当前基础上更全面一点的了解 ES6,同时对一些较疑难又非常棒的 API 能更深入的理解,我有个想法就是重新 “画轮子” 。

虽然个人对这件事有点犹豫,犹豫的点主要因为现在已不乏 ES6 的文档、资料和问题解答;各路牛人一不小心看到本人整理ES6资料,也许因为理解不到位、或者无用功而扔砖头;最后,自认为写东西不擅长,是个耗时的苦差事。

但是,今天还是开始了。

首先,期望的是收获对 ES6 更全面的了解,其间若能领略一些 ES6语言设计风格,窥视 JavaScript 技术走向,那也算很好的收获。另外,新入门的朋友若能因此在 ES6 的学习上做一个轻重缓急的划分,或者收获些许领悟,也是极好。

好了,重新 “画轮子”,主要基于以下思路:

  • 强调从 ES5ES6的差异,进行知识迭代
  • 更注重于 API 的设计角度来分析新增特性
  • 对技术点进行边界管理,将疑难的点先划在边界之外,之后再专门突破

纵观 ES6各种新特性,整理出以下一份清单:

1、基本类型的扩展
2、新增类型和数据结构
3、模块(Module)
4、Promise
5、异步同步化方案 async/await
6、其他偏语言层面的能力开放

大概以 Promise 为分界,分类靠前的更基础,使用频率更高,相对来说也比较简单;靠后的是更偏向新增特性,使用频率稍低,但它们能量可不小。而 Promise 本身,则是一个非常棒的 API。

以下内容是上述条目的展开,各有详略。

基本类型的扩展

很多前端面试,都会问到 JS 的基本类型的理解。

没错,在 ES5 的基础之上, ES6 对这些基本类型进行了一些扩展,扩展并不是平白新增特性,而是将 ES5 的一些高频使用方式等做了封装,并 API 化了。

下面依次来看看字符串数值正则数组函数对象以及解构赋值这种新的声明变量的方式。当然,正则不属于基本类型,这里是借助 “类型的扩展” 这个话题来讲的;另外,函数和对象的扩展是比较重要的部分,会讲得稍详细些。

字符串的扩展

字符串的扩展,主要包括:

  • 新增一些方法
  • 增强对一些其他编码(特别是 Unicode)的识别能力及计算能力
  • 增加模板字符串。

一、新增的方法

新增的方法 includes(), startsWith(), endsWith() 等基本上是对 indexOf() 方法的封装。

let str = 'hello world';

//ES6 的 API 直接调用,语义清晰简洁
str.endsWith('d');
// => true

//ES5 表达同样的意思却要写一堆
str.indexOf('d') === str.length - 1;

新增的 repeat() 也是一个简单明了的方法。如果想将一个字符串重复 copy 若干次,在 ES5 是大概率得想到用 for 循环。

//庞大的循环语句,重复字符 `x` 共 n 次
function repeat (n) {
    var str = '';
    for(var i = 0; i < n; i++) {
        str += 'x';
    }
    return str;
}
var str = repeat(10);

//就算灵机一动,使用其他途径,有点别扭
var str = new Array(10).join('x');

//ES6 推出一个新方法,封装这一高频需求
let str = 'x'.repeat(10);

// 当然这个方法的输入参数有类型等要求,不在此讨论范围

虽然本质上都绕不开循环,但是 repeat() 方法明显更优雅。从语言级别来封装这一使用场景,能从更大规模上节省不少的代码信息量。

二、模板字符串

模板字符串是个非常好的东西,也是我们非常高频使用的工具。用 backbone.js 那会,就有很多前端同事,以及从其他语言 “入侵” 的同事抱怨过字符串拼接中的一大堆 + 号和恼人的 " '号了。

//恼人的 `+` 号和引号
function welcome (name, place) {
    return 'Hello ' + name + ', welcome to ' + place + '!';
}

//ES6 下 瞬间清爽了
function welcome (name, place) {
    return `Hello ${name}, welcome to ${place}!`;
}

JS 代码中内嵌很多模板片段时,模板字符串无论是处理还是维护,都方便很多。

此外,使用模板字符串可以轻易的实现一个模板库出来,此处也不再展开。

三、编码格式(如 Unicode)的相关扩展

最后,扩展了字符串对其他编码格式的识别和处理能力,这点目前需求最多(个人感觉)的恐怕的是 i18n 即多语言处理上。这部分的 API 也没有什么难度,在使用时在把玩一下即可,平时无需增加认知负担。

数值的扩展

数值的扩展,主要的动作是:

  • 将一些 window 下关于数值类型的 API 迁移到了更为合理的地方(Number 上)
  • Number 对象上继续新增了几个方法
  • Math 对象上,则扩展了更多的常规计算函数
  • 最后还增加了一些对整型、浮点型数据溢出的应对方法。

一、迁移并提纯

数值的扩展,毫无认知压力以及让人容易理解的是上述的第一个动作:将合适的 API 从错误的地方(window 对象中)移到正确的地方(Number 对象)下管理。这个如标题所说是 “迁移”。

那“提纯”怎么说?——”纯函数”,对的。如果还不具备纯函数知识的朋友,若看过本人前一篇文章《走向 JavaScript 函数式编程》 就知道,迁移后的方法,变成纯函数了——它不再对参数(的类型)形成 “副作用” 了。

// 有限的数值,就是有限的
Number.isFinite(9999); // => true

//足够大、无限数、非数值它就不是有限的
Number.isFinite(Math.pow(99, 9999));  // => false
Number.isFinite(Infinity); // => false
Number.isFinite('999');  // => false

// 对于 NaN 判断 true
Number.isNaN(NaN); // => true
Number.isNaN(999 / NaN); // => true

// 除 NaN 以外一切都判断 false
Number.isNaN('NaN'); // => false
Number.isNaN(true) // => false

// 这两个 API 就是为了得到数值,放在 Number 更合理
Number.parseInt();
Number.parseFloat();

移动后的方法 Number.isFinite()、Number.isNaN() 较之先前也更纯粹,它们的参数不会进行类型自动转化,是字符串就是字符串,是布尔值就是布尔值,不会自作主张转化一下,再去判断。

而原有的挂载在 window 下的这俩方法的行为让人捉摸不透。

// 莫名其妙的对参数进行了类型转化
isFinite('999'); // => true
isNaN('NaN'); // => true

迁移本身的好处是,逐步减少全局性方法,使得语言逐步走向模块化。本身这点也是 ES6 所倡导的。

二、Number 对象新增计算方法和常量

迁移动作如果是对历史的修正的话,下边就是通过新增更多的元素,来丰富 JavaScript 的数值计算能力。

// 判断参数是否是数值中的整数
Number.isInteger(28.0); // => true
Number.isSafeInteger(Infinity) // false

// 新增常量,都挂在 Number 对象下
Number.EPSILON === 2.220446049250313e-16; // => true
Number.MAX_SAFE_INTEGER === 9007199254740991; // => true
Number.MIN_SAFE_INTEGER === -Number.MAX_SAFE_INTEGER; // => true

Number.isInteger(),Number.isSafeInteger() 也是 “提纯” 后的方法,即不会擅自转化参数类型。

上述常量也非常好理解。它们的添加,给实际编程时减少了很多负担,因为现在任何时候,再也不用自己写一个 const MAX_INTEGER = Math.pow(2, 53)了,即拿即用就好了。

三、Math 对象新增方法

// 剪掉小数这个尾巴
Math.trunc(3.141592653);

// 这是新增的一个有用的数学函数
Math.sign(Math.PI/2);  // => 1

// 这是 ES5 就有的三角函数
Math.sin(Math.PI/2);  // => 1

// 求立方根
Math.cbrt(27); // => 3

// 平方和的平方根 (计算空间距离很方便)
Math.hypot(1, 4, 8); // => 9

//还有很多对数函数的方法、双曲线函数的方法
//...

一些对数、双曲线函数的方法——除非是一些数据处理、科研等项目——在平时并不那么常用,因此不再罗列和展开描述了。

四、数值的其他扩展

数值的扩展,最后还增加了一些对整型、浮点型数据溢出的应对方法。这个需要在平日处理数据是特别注意一下。如果不清楚,则在需要的时候查看下文档,就能轻易掌握。

正则表达式的扩展

正则表达式也许平时编程用得不多,当然也不见得,因项目而异。如果觉得正则实在枯燥的朋友,可以先跳过本知识点,以后按需再学。

对于剩下的观众,我们继续。ES6 对正则的一些扩展还是非常有意义的,也是有章可循的。大概和前文关联起来解读,有这些扩展点:

  • 迁移。像数值的一些方法那样,将正则相关的一些方法归属到合理的对象之下;
  • 增加了若干修饰符。尤其是修饰符 u,这和字符串的编码格式的扩展遥相呼应;
  • 后行断言。这是对正则能力完备性的一个修复;
  • 专门扩展了正则匹配时对 Unicode 字符的一些处理。这仍然呼应上述第 2 条;
  • 属名组匹配。这是为语义清晰化做的一个扩展。

本节只挑 迁移, 后行断言属名组匹配 来讲,Unicode 再一次被冷落。

一、迁移

一时也找不到专业的描述,所以类似挪位置的都姑且叫 “迁移”。

字符串的与正则相关的方法,str.match()str.replace()str.search()str.split() 原本都放在 String.prototype 下实现,现在 ES6 有更为合理的归宿:

String.prototype.match() 
    => RegExp.prototype[Symbol.match]();
    
String.prototype.replace() 
    => RegExp.prototype[Symbol.replace]();
    
String.prototype.search() 
    => RegExp.prototype[Symbol.search]();
    
String.prototype.split() 
    => RegExp.prototype[Symbol.split]();

字符串的上述方法,最终调用的是 RegExp 上实现的方法。可见, ES6 内部还是做了很多优化调整的。

二、后行断言

ES5 正则表达式已经提供这些断言匹配:(?:pattern)(?=pattern)(?!pattern)。仅拿第二个来举例:

// (?=26|27|28) 好比正则中的正则表达式
let reg = /Today(?=26|27|28)/g; 
// => 它能匹配到 'Today27' 中的 'Today'
// => 但无法匹配到 'Today29' 中的 'Today'
// => (?=pattern) 表达式只起到辅助验证作用,如果验证通过,则其辅佐的对象将成为最终的结果

可以看到,这种断言是先有主体,再有辅助表达式,意味着——如果辅助表达式匹配成功——前半部分就是本次全部匹配能够获取的目标。

那问题来了,我想匹配到后面的目标,而让前半部分成为辅佐匹配,那岂不是难以办到?确实,笔者曾经就遇到过这个麻烦。

现在 ES6 正好弥补了这个缺陷,提供了 “后行断言” 的规则,只要在 “先行断言” 的问号后加上 ‘<‘ 就是了。它们分别是:(?<:pattern)(?<=pattern)(?<!pattern)

let reg = /(?<=26|27|28)Today/g; 
// => 它能匹配到 '27Today' 中的 'Today'

三、属名组匹配

ES5 其实也有属名组,就是匹配到一组 ( ) 中的值时,可以用特定的字符 $1/$2/$3 来表示。

这个看起来像模板字符串的东西,大家想必都见过:

// $1/$2/$3 依次捕获正则中的 '( )' 括号匹配到的值
let reg = /(\d{4})-(\d{2})-(\d{2})/;
'2017-8-27'.replace(reg, '$1/$2/$3');
// => '2017/8/27'

但是这样仍然非常不直观,几乎没有语义化可言。ES6 的做法是,允许在匹配模式前加一个签名比如 <year>,表明将来匹配到的这个内容,直接赋值给了 year 变量。

let reg = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
let matched = reg.exec('2017-8-27');
let year = matched.groups.year; // => 2017
let month = matched.groups.month; // => 8
let day = matched.groups.day; // => 27

正则还有很多扩展没有讲到,但以上应该是使用频率比较高的几个部分了。另外,ES6 对正则的扩展已深入到比较细致的部分,也就是赋予了正则表达式更多的编程乐趣。

总结

以上对 JavaScript2015 基本类型中的字符串数值以及 正则的扩展部分进行了梳理,该部分比较简单,也非常琐碎。但也能充分凸显 ES6 相比 ES5 细节处变化之大。

下一篇《基本类型的扩展(二)》会讲到数组、函数、对象的扩展。敬请期待。

    原文作者:Jeremy_young
    原文地址: https://www.jianshu.com/p/995c56739a8b
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞