從去年開始學(xué)習(xí)及使用 ES6
語法,后得益于項目的推動,到現(xiàn)在已較廣泛的使用 ES6
寫代碼了右蕊。
我們知道,ES6
較之 ES5
吮螺,新增特性非常之多饶囚,變化非常大,幾乎像是兩種不同語言鸠补。盡管現(xiàn)在 ES6
滿大街了萝风,但仍有許多有用的特性很少觸及。另外莫鸭,對于已使用的特性闹丐,有的使用頻率高,有的使用頻率比較低被因。
為了在當(dāng)前基礎(chǔ)上更全面一點的了解 ES6
,同時對一些較疑難又非常棒的 API 能更深入的理解衫仑,我有個想法就是重新 “畫輪子” 梨与。
雖然個人對這件事有點猶豫,猶豫的點主要因為現(xiàn)在已不乏 ES6
的文檔文狱、資料和問題解答粥鞋;各路牛人一不小心看到本人整理ES6
資料,也許因為理解不到位瞄崇、或者無用功而扔磚頭呻粹;最后壕曼,自認(rèn)為寫東西不擅長,是個耗時的苦差事等浊。
但是腮郊,今天還是開始了。
首先筹燕,期望的是收獲對 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)賦值
這種新的聲明變量的方式。當(dāng)然次氨,正則不屬于基本類型蔽介,這里是借助 “類型的擴展” 這個話題來講的;另外煮寡,函數(shù)和對象的擴展是比較重要的部分虹蓄,會講得稍詳細(xì)些。
字符串的擴展
字符串的擴展幸撕,主要包括:
- 新增一些方法
- 增強對一些其他編碼(特別是
Unicode
)的識別能力及計算能力 - 增加模板字符串薇组。
一、新增的方法
新增的方法 includes()
, startsWith()
, endsWith()
等基本上是對 indexOf()
方法的封裝坐儿。
let str = 'hello world';
//ES6 的 API 直接調(diào)用律胀,語義清晰簡潔
str.endsWith('d');
// => true
//ES5 表達(dá)同樣的意思卻要寫一堆
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);
// 當(dāng)然這個方法的輸入?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
也沒有什么難度暇屋,在使用時在把玩一下即可似袁,平時無需增加認(rèn)知負(fù)擔(dān)。
數(shù)值的擴展
數(shù)值的擴展咐刨,主要的動作是:
- 將一些
window
下關(guān)于數(shù)值類型的API
遷移到了更為合理的地方(Number
上) - 在
Number
對象上繼續(xù)新增了幾個方法 -
Math
對象上昙衅,則擴展了更多的常規(guī)計算函數(shù) - 最后還增加了一些對整型、浮點型數(shù)據(jù)溢出的應(yīng)對方法定鸟。
一而涉、遷移并提純
數(shù)值的擴展,毫無認(rèn)知壓力以及讓人容易理解的是上述的第一個動作:將合適的 API
從錯誤的地方(window
對象中)移到正確的地方(Number
對象)下管理联予。這個如標(biāo)題所說是 "遷移"啼县。
那“提純”怎么說?——"純函數(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ù)類型漱受。
上述常量也非常好理解络凿。它們的添加,給實際編程時減少了很多負(fù)擔(dān)昂羡,因為現(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ù)是特別注意一下。如果不清楚泣刹,則在需要的時候查看下文檔助析,就能輕易掌握。
正則表達(dá)式的擴展
正則表達(dá)式也許平時編程用得不多椅您,當(dāng)然也不見得外冀,因項目而異。如果覺得正則實在枯燥的朋友掀泳,可以先跳過本知識點雪隧,以后按需再學(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
正則表達(dá)式已經(jīng)提供這些斷言匹配:(?:pattern)
嚼蚀、(?=pattern)
禁灼、(?!pattern)
。僅拿第二個來舉例:
// (?=26|27|28) 好比正則中的正則表達(dá)式
let reg = /Today(?=26|27|28)/g;
// => 它能匹配到 'Today27' 中的 'Today'
// => 但無法匹配到 'Today29' 中的 'Today'
// => (?=pattern) 表達(dá)式只起到輔助驗證作用轿曙,如果驗證通過弄捕,則其輔佐的對象將成為最終的結(jié)果
可以看到僻孝,這種斷言是先有主體,再有輔助表達(dá)式守谓,意味著——如果輔助表達(dá)式匹配成功——前半部分就是本次全部匹配能夠獲取的目標(biāo)穿铆。
那問題來了,我想匹配到后面的目標(biāo)斋荞,而讓前半部分成為輔佐匹配荞雏,那豈不是難以辦到?確實平酿,筆者曾經(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
對正則的擴展已深入到比較細(xì)致的部分身冀,也就是賦予了正則表達(dá)式更多的編程樂趣。
總結(jié)
以上對 JavaScript2015
基本類型中的字符串
括享、數(shù)值
以及 正則
的擴展部分進行了梳理搂根,該部分比較簡單,也非沉逑剑瑣碎剩愧。但也能充分凸顯 ES6
相比 ES5
細(xì)節(jié)處變化之大。
下一篇《基本類型的擴展(二)》會講到數(shù)組娇斩、函數(shù)仁卷、對象的擴展穴翩。敬請期待。