从去年开始学习及使用 ES6
语法,后得益于项目的推动,到现在已较广泛的使用 ES6
写代码了。
我们知道,ES6
较之 ES5
,新增特性非常之多,变化非常大,几乎像是两种不同语言。尽管现在 ES6
满大街了,但仍有许多有用的特性很少触及。另外,对于已使用的特性,有的使用频率高,有的使用频率比较低。
为了在当前基础上更全面一点的了解 ES6
,同时对一些较疑难又非常棒的 API 能更深入的理解,我有个想法就是重新 “画轮子” 。
虽然个人对这件事有点犹豫,犹豫的点主要因为现在已不乏 ES6
的文档、资料和问题解答;各路牛人一不小心看到本人整理ES6
资料,也许因为理解不到位、或者无用功而扔砖头;最后,自认为写东西不擅长,是个耗时的苦差事。
但是,今天还是开始了。
首先,期望的是收获对 ES6
更全面的了解,其间若能领略一些 ES6
语言设计风格,窥视 JavaScript 技术走向,那也算很好的收获。另外,新入门的朋友若能因此在 ES6
的学习上做一个轻重缓急的划分,或者收获些许领悟,也是极好。
好了,重新 “画轮子”,主要基于以下思路:
- 强调从
ES5
到ES6
的差异,进行知识迭代 - 更注重于 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
细节处变化之大。
下一篇《基本类型的扩展(二)》会讲到数组、函数、对象的扩展。敬请期待。