從去年開始學(xué)習(xí)及使用 ES6
語法,后得益于項目的推動,到現(xiàn)在已較廣泛的使用 ES6
寫代碼了。
我們知道,ES6
較之 ES5
,新增特性非常之多,變化非常大,幾乎像是兩種不同語言。盡管現(xiàn)在 ES6
滿大街了,但仍有許多有用的特性很少觸及。另外,對于已使用的特性,有的使用頻率高,有的使用頻率比較低。
為了在當前基礎(chǔ)上更全面一點的了解 ES6
,同時對一些較疑難又非常棒的 API 能更深入的理解,我有個想法就是重新 “畫輪子” 。
雖然個人對這件事有點猶豫,猶豫的點主要因為現(xiàn)在已不乏 ES6
的文檔、資料和問題解答;各路牛人一不小心看到本人整理ES6
資料,也許因為理解不到位、或者無用功而扔磚頭;最后,自認為寫東西不擅長,是個耗時的苦差事。
但是,今天還是開始了。
首先,期望的是收獲對 ES6
更全面的了解,其間若能領(lǐng)略一些 ES6
語言設(shè)計風(fēng)格,窺視 JavaScript 技術(shù)走向,那也算很好的收獲。另外,新入門的朋友若能因此在 ES6
的學(xué)習(xí)上做一個輕重緩急的劃分,或者收獲些許領(lǐng)悟,也是極好。
好了,重新 “畫輪子”,主要基于以下思路:
- 強調(diào)從
ES5
到ES6
的差異,進行知識迭代 - 更注重于 API 的設(shè)計角度來分析新增特性
- 對技術(shù)點進行邊界管理,將疑難的點先劃在邊界之外,之后再專門突破
縱觀 ES6
各種新特性,整理出以下一份清單:
1、基本類型的擴展
2、新增類型和數(shù)據(jù)結(jié)構(gòu)
3、模塊(Module)
4、Promise
5、異步同步化方案 async/await
6、其他偏語言層面的能力開放
大概以 Promise
為分界,分類靠前的更基礎(chǔ),使用頻率更高,相對來說也比較簡單;靠后的是更偏向新增特性,使用頻率稍低,但它們能量可不小。而 Promise
本身,則是一個非常棒的 API。
以下內(nèi)容是上述條目的展開,各有詳略。
基本類型的擴展
很多前端面試,都會問到 JS
的基本類型的理解。
沒錯,在 ES5
的基礎(chǔ)之上, ES6
對這些基本類型進行了一些擴展,擴展并不是平白新增特性,而是將 ES5
的一些高頻使用方式等做了封裝,并 API
化了。
下面依次來看看字符串
、數(shù)值
、正則
、數(shù)組
、函數(shù)
、對象
以及解構(gòu)賦值
這種新的聲明變量的方式。當然,正則不屬于基本類型,這里是借助 “類型的擴展” 這個話題來講的;另外,函數(shù)和對象的擴展是比較重要的部分,會講得稍詳細些。
字符串的擴展
字符串的擴展,主要包括:
- 新增一些方法
- 增強對一些其他編碼(特別是
Unicode
)的識別能力及計算能力 - 增加模板字符串。
一、新增的方法
新增的方法 includes()
, startsWith()
, endsWith()
等基本上是對 indexOf()
方法的封裝。
let str = 'hello world';
//ES6 的 API 直接調(diào)用,語義清晰簡潔
str.endsWith('d');
// => true
//ES5 表達同樣的意思卻要寫一堆
str.indexOf('d') === str.length - 1;
新增的 repeat()
也是一個簡單明了的方法。如果想將一個字符串重復(fù) copy 若干次,在 ES5
是大概率得想到用 for
循環(huán)。
//龐大的循環(huán)語句,重復(fù)字符 `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);
// 當然這個方法的輸入?yún)?shù)有類型等要求,不在此討論范圍
雖然本質(zhì)上都繞不開循環(huán),但是 repeat()
方法明顯更優(yōu)雅。從語言級別來封裝這一使用場景,能從更大規(guī)模上節(jié)省不少的代碼信息量。
二、模板字符串
模板字符串是個非常好的東西,也是我們非常高頻使用的工具。用 backbone.js
那會,就有很多前端同事,以及從其他語言 “入侵” 的同事抱怨過字符串拼接中的一大堆 +
號和惱人的 " '
號了。
//惱人的 `+` 號和引號
function welcome (name, place) {
return 'Hello ' + name + ', welcome to ' + place + '!';
}
//ES6 下 瞬間清爽了
function welcome (name, place) {
return `Hello ${name}, welcome to ${place}!`;
}
在 JS
代碼中內(nèi)嵌很多模板片段時,模板字符串無論是處理還是維護,都方便很多。
此外,使用模板字符串可以輕易的實現(xiàn)一個模板庫出來,此處也不再展開。
三、編碼格式(如 Unicode)的相關(guān)擴展
最后,擴展了字符串對其他編碼格式的識別和處理能力,這點目前需求最多(個人感覺)的恐怕的是 i18n
即多語言處理上。這部分的 API
也沒有什么難度,在使用時在把玩一下即可,平時無需增加認知負擔。
數(shù)值的擴展
數(shù)值的擴展,主要的動作是:
- 將一些
window
下關(guān)于數(shù)值類型的API
遷移到了更為合理的地方(Number
上) - 在
Number
對象上繼續(xù)新增了幾個方法 -
Math
對象上,則擴展了更多的常規(guī)計算函數(shù) - 最后還增加了一些對整型、浮點型數(shù)據(jù)溢出的應(yīng)對方法。
一、遷移并提純
數(shù)值的擴展,毫無認知壓力以及讓人容易理解的是上述的第一個動作:將合適的 API
從錯誤的地方(window
對象中)移到正確的地方(Number
對象)下管理。這個如標題所說是 "遷移"。
那“提純”怎么說?——"純函數(shù)",對的。如果還不具備純函數(shù)知識的朋友,若看過本人前一篇文章《走向 JavaScript 函數(shù)式編程》 就知道,遷移后的方法,變成純函數(shù)了——它不再對參數(shù)(的類型)形成 “副作用” 了。
// 有限的數(shù)值,就是有限的
Number.isFinite(9999); // => true
//足夠大、無限數(shù)、非數(shù)值它就不是有限的
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 就是為了得到數(shù)值,放在 Number 更合理
Number.parseInt();
Number.parseFloat();
移動后的方法 Number.isFinite()、Number.isNaN()
較之先前也更純粹,它們的參數(shù)不會進行類型自動轉(zhuǎn)化,是字符串就是字符串,是布爾值就是布爾值,不會自作主張轉(zhuǎn)化一下,再去判斷。
而原有的掛載在 window
下的這倆方法的行為讓人捉摸不透。
// 莫名其妙的對參數(shù)進行了類型轉(zhuǎn)化
isFinite('999'); // => true
isNaN('NaN'); // => true
遷移本身的好處是,逐步減少全局性方法,使得語言逐步走向模塊化。本身這點也是 ES6
所倡導(dǎo)的。
二、Number 對象新增計算方法和常量
遷移動作如果是對歷史的修正的話,下邊就是通過新增更多的元素,來豐富 JavaScript
的數(shù)值計算能力。
// 判斷參數(shù)是否是數(shù)值中的整數(shù)
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()
也是 “提純” 后的方法,即不會擅自轉(zhuǎn)化參數(shù)類型。
上述常量也非常好理解。它們的添加,給實際編程時減少了很多負擔,因為現(xiàn)在任何時候,再也不用自己寫一個 const MAX_INTEGER = Math.pow(2, 53)
了,即拿即用就好了。
三、Math 對象新增方法
// 剪掉小數(shù)這個尾巴
Math.trunc(3.141592653);
// 這是新增的一個有用的數(shù)學(xué)函數(shù)
Math.sign(Math.PI/2); // => 1
// 這是 ES5 就有的三角函數(shù)
Math.sin(Math.PI/2); // => 1
// 求立方根
Math.cbrt(27); // => 3
// 平方和的平方根 (計算空間距離很方便)
Math.hypot(1, 4, 8); // => 9
//還有很多對數(shù)函數(shù)的方法、雙曲線函數(shù)的方法
//...
一些對數(shù)、雙曲線函數(shù)的方法——除非是一些數(shù)據(jù)處理、科研等項目——在平時并不那么常用,因此不再羅列和展開描述了。
四、數(shù)值的其他擴展
數(shù)值的擴展,最后還增加了一些對整型、浮點型數(shù)據(jù)溢出的應(yīng)對方法。這個需要在平日處理數(shù)據(jù)是特別注意一下。如果不清楚,則在需要的時候查看下文檔,就能輕易掌握。
正則表達式的擴展
正則表達式也許平時編程用得不多,當然也不見得,因項目而異。如果覺得正則實在枯燥的朋友,可以先跳過本知識點,以后按需再學(xué)。
對于剩下的觀眾,我們繼續(xù)。ES6
對正則的一些擴展還是非常有意義的,也是有章可循的。大概和前文關(guān)聯(lián)起來解讀,有這些擴展點:
- 遷移。像數(shù)值的一些方法那樣,將正則相關(guān)的一些方法歸屬到合理的對象之下;
- 增加了若干修飾符。尤其是修飾符
u
,這和字符串的編碼格式的擴展遙相呼應(yīng); - 后行斷言。這是對正則能力完備性的一個修復(fù);
- 專門擴展了正則匹配時對
Unicode
字符的一些處理。這仍然呼應(yīng)上述第 2 條; - 屬名組匹配。這是為語義清晰化做的一個擴展。
本節(jié)只挑 遷移
, 后行斷言
,屬名組匹配
來講,Unicode
再一次被冷落。
一、遷移
一時也找不到專業(yè)的描述,所以類似挪位置的都姑且叫 “遷移”。
字符串的與正則相關(guān)的方法,str.match()
、str.replace()
、str.search()
和 str.split()
原本都放在 String.prototype
下實現(xiàn),現(xiàn)在 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]();
字符串的上述方法,最終調(diào)用的是 RegExp
上實現(xiàn)的方法。可見, ES6
內(nèi)部還是做了很多優(yōu)化調(diào)整的。
二、后行斷言
ES5
正則表達式已經(jīng)提供這些斷言匹配:(?:pattern)
、(?=pattern)
、(?!pattern)
。僅拿第二個來舉例:
// (?=26|27|28) 好比正則中的正則表達式
let reg = /Today(?=26|27|28)/g;
// => 它能匹配到 'Today27' 中的 'Today'
// => 但無法匹配到 'Today29' 中的 'Today'
// => (?=pattern) 表達式只起到輔助驗證作用,如果驗證通過,則其輔佐的對象將成為最終的結(jié)果
可以看到,這種斷言是先有主體,再有輔助表達式,意味著——如果輔助表達式匹配成功——前半部分就是本次全部匹配能夠獲取的目標。
那問題來了,我想匹配到后面的目標,而讓前半部分成為輔佐匹配,那豈不是難以辦到?確實,筆者曾經(jīng)就遇到過這個麻煩。
現(xiàn)在 ES6
正好彌補了這個缺陷,提供了 “后行斷言” 的規(guī)則,只要在 “先行斷言” 的問號后加上 '<' 就是了。它們分別是:(?<: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>
,表明將來匹配到的這個內(nèi)容,直接賦值給了 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
正則還有很多擴展沒有講到,但以上應(yīng)該是使用頻率比較高的幾個部分了。另外,ES6
對正則的擴展已深入到比較細致的部分,也就是賦予了正則表達式更多的編程樂趣。
總結(jié)
以上對 JavaScript2015
基本類型中的字符串
、數(shù)值
以及 正則
的擴展部分進行了梳理,該部分比較簡單,也非常瑣碎。但也能充分凸顯 ES6
相比 ES5
細節(jié)處變化之大。
下一篇《基本類型的擴展(二)》會講到數(shù)組、函數(shù)、對象的擴展。敬請期待。