22個ES6面試、復習干貨知識點匯總
近期在復習ES6,針對ES6新的知識點,以問答形式整理一個全面知識和問題匯總。
一、問:ES6是什么,為什么要學習它,不學習ES6會怎么樣?
答:ES6是新一代的JS語言標準,對分JS語言核心內容做了升級優(yōu)化,規(guī)范了JS使用標準,新增了JS原生方法,使得JS使用更加規(guī)范,更加優(yōu)雅,更適合大型應用的開發(fā)。學習ES6是成為專業(yè)前端正規(guī)軍的必經之路。不學習ES6也可以寫代碼打鬼子,但是最多只能當個游擊隊長。
二、問:ES5、ES6和ES2015有什么區(qū)別?
答:ES2015特指在2015年發(fā)布的新一代JS語言標準,ES6泛指下一代JS語言標準,包含ES2015、ES2016、ES2017、ES2018等。現階段在絕大部分場景下,ES2015默認等同ES6。ES5泛指上一代語言標準。ES2015可以理解為ES5和ES6的時間分界線。
三、問:babel是什么,有什么作用?
答:babel是一個 ES6 轉碼器,可以將 ES6 代碼轉為 ES5 代碼,以便兼容那些還沒支持ES6的平臺。
四、問:let有什么用,有了var為什么還要用let?
答:在ES6之前,聲明變量只能用var,var方式聲明變量其實是很不合理的,準確的說,是因為ES5里面沒有塊級作用域是很不合理的,甚至可以說是一個語言層面的bug(這也是很多c++、java開發(fā)人員看不懂,也瞧不起JS語言的劣勢之一)。沒有塊級作用域會帶來很多難以理解的問題,比如for循環(huán)var變量泄露,變量覆蓋等問題。let 聲明的變量擁有自己的塊級作用域,且修復了var聲明變量帶來的變量提升問題。
五、問:舉一些ES6對String字符串類型做的常用升級優(yōu)化?
答:
1、優(yōu)化部分:
ES6新增了字符串模板,在拼接大段字符串時,用反斜杠(`)取代以往的字符串相加的形式,能保留所有空格和換行,使得字符串拼接看起來更加直觀,更加優(yōu)雅。
2、升級部分:
ES6在String原型上新增了includes()方法,用于取代傳統(tǒng)的只能用indexOf查找包含字符的方法(indexOf返回-1表示沒查到不如includes方法返回false更明確,語義更清晰), 此外還新增了startsWith(), endsWith(), padStart(),padEnd(),repeat()等方法,可方便的用于查找,補全字符串。
六、問:舉一些ES6對Array數組類型做的常用升級優(yōu)化?
答:
1、優(yōu)化部分:
a.?數組解構賦值。ES6可以直接以let [a,b,c] = [1,2,3]形式進行變量賦值,在聲明較多變量時,不用再寫很多l(xiāng)et(var),且映射關系清晰,且支持賦默認值。
b.?擴展運算符。ES6新增的擴展運算符(...)(重要),可以輕松的實現數組和松散序列的相互轉化,可以取代arguments對象和apply方法,輕松獲取未知參數個數情況下的參數集合。(尤其是在ES5中,arguments并不是一個真正的數組,而是一個類數組的對象,但是擴展運算符的逆運算卻可以返回一個真正的數組)。擴展運算符還可以輕松方便的實現數組的復制和解構賦值(let a = [2,3,4]; let b = [...a])。
2、升級部分:
ES6在Array原型上新增了find()方法,用于取代傳統(tǒng)的只能用indexOf查找包含數組項目的方法,且修復了indexOf查找不到NaN的bug([NaN].indexOf(NaN) === -1).此外還新增了copyWithin(), includes(), fill(),flat()等方法,可方便的用于字符串的查找,補全,轉換等。
七、問:舉一些ES6對Number數字類型做的常用升級優(yōu)化?
答:
1、優(yōu)化部分:
ES6在Number原型上新增了isFinite(), isNaN()方法,用來取代傳統(tǒng)的全局isFinite(), isNaN()方法檢測數值是否有限、是否是NaN。ES5的isFinite(), isNaN()方法都會先將非數值類型的參數轉化為Number類型再做判斷,這其實是不合理的,最造成isNaN('NaN') === true的奇怪行為--'NaN'是一個字符串,但是isNaN卻說這就是NaN。而Number.isFinite()和Number.isNaN()則不會有此類問題(Number.isNaN('NaN') === false)。(isFinite()同上)
2、升級部分:
ES6在Math對象上新增了Math.cbrt(),trunc(),hypot()等等較多的科學計數法運算方法,可以更加全面的進行立方根、求和立方根等等科學計算。
八、問:舉一些ES6對Object類型做的常用升級優(yōu)化?(重要)
答:
1、優(yōu)化部分:
a.?對象屬性變量式聲明。ES6可以直接以變量形式聲明對象屬性或者方法,。比傳統(tǒng)的鍵值對形式聲明更加簡潔,更加方便,語義更加清晰。
let?[apple,?orange]?=?['red?appe',?'yellow?orange'];
let?myFruits?=?{apple,?orange};????//?let?myFruits?=?{apple:?'red?appe',?orange:?'yellow?orange'};
尤其在對象解構賦值(見優(yōu)化部分b.)或者模塊輸出變量時,這種寫法的好處體現的最為明顯:
let?{keys,?values,?entries}?=?Object;
let?MyOwnMethods?=?{keys,?values,?entries};?//?let?MyOwnMethods?=?{keys:?keys,?values:?values,?entries:?entries}
可以看到屬性變量式聲明屬性看起來更加簡潔明了。方法也可以采用簡潔寫法:
let?es5Fun?=?{
????method:?function(){}
};?
let?es6Fun?=?{
????method(){}
}
b.?對象的解構賦值。ES6對象也可以像數組解構賦值那樣,進行變量的解構賦值:
let?{apple,?orange}?=?{apple:?'red?appe',?orange:?'yellow?orange'};
c.?對象的擴展運算符(...)。ES6對象的擴展運算符和數組擴展運算符用法本質上差別不大,畢竟數組也就是特殊的對象。對象的擴展運算符一個最常用也最好用的用處就在于可以輕松的取出一個目標對象內部全部或者部分的可遍歷屬性,從而進行對象的合并和分解。
let?{apple,?orange,?...otherFruits}?=?{apple:?'red?apple',?orange:?'yellow?orange',?grape:?'purple?grape',?peach:?'sweet?peach'};?
//?otherFruits??{grape:?'purple?grape',?peach:?'sweet?peach'}
//?注意:?對象的擴展運算符用在解構賦值時,擴展運算符只能用在最后一個參數(otherFruits后面不能再跟其他參數)
let?moreFruits?=?{watermelon:?'nice?watermelon'};
let?allFruits?=?{apple,?orange,?...otherFruits,?...moreFruits};
d.?super 關鍵字。ES6在Class類里新增了類似this的關鍵字super。同this總是指向當前函數所在的對象不同,super關鍵字總是指向當前函數所在對象的原型對象。
2、升級部分:
a.?ES6在Object原型上新增了is()方法,做兩個目標對象的相等比較,用來完善'==='方法。'==='方法中NaN === NaN //false其實是不合理的,Object.is修復了這個小bug。(Object.is(NaN, NaN) // true)
b.?ES6在Object原型上新增了assign()方法,用于對象新增屬性或者多個對象合并。
const?target?=?{?a:?1?};
const?source1?=?{?b:?2?};
const?source2?=?{?c:?3?};
Object.assign(target,?source1,?source2);
target?//?{a:1,?b:2,?c:3}
注意: assign合并的對象target只能合并source1、source2中的自身屬性,并不會合并source1、source2中的繼承屬性,也不會合并不可枚舉的屬性,且無法正確復制get和set屬性(會直接執(zhí)行get/set函數,取return的值)。
c.?ES6在Object原型上新增了getOwnPropertyDescriptors()方法,此方法增強了ES5中getOwnPropertyDescriptor()方法,可以獲取指定對象所有自身屬性的描述對象。結合defineProperties()方法,可以完美復制對象,包括復制get和set屬性。
d.?ES6在Object原型上新增了getPrototypeOf()和setPrototypeOf()方法,用來獲取或設置當前對象的prototype對象。這個方法存在的意義在于,ES5中獲取設置prototype對像是通過__proto__屬性來實現的,然而__proto__屬性并不是ES規(guī)范中的明文規(guī)定的屬性,只是瀏覽器各大產商“私自”加上去的屬性,只不過因為適用范圍廣而被默認使用了,再非瀏覽器環(huán)境中并不一定就可以使用,所以為了穩(wěn)妥起見,獲取或設置當前對象的prototype對象時,都應該采用ES6新增的標準用法。
e.?ES6在Object原型上還新增了Object.keys(),Object.values(),Object.entries()方法,用來獲取對象的所有鍵、所有值和所有鍵值對數組。
九、問:舉一些ES6對Function函數類型做的常用升級優(yōu)化?(重要)
答:
1、優(yōu)化部分:
a.?箭頭函數**(核心)**。箭頭函數是ES6核心的升級項之一,箭頭函數里沒有自己的this,這改變了以往JS函數中最讓人難以理解的this運行機制。主要優(yōu)化點:
Ⅰ. ???箭頭函數內的this指向的是函數定義時所在的對象,而不是函數執(zhí)行時所在的對象。ES5函數里的this總是指向函數執(zhí)行時所在的對象,這使得在很多情況下this的指向變得很難理解,尤其是非嚴格模式情況下,this有時候會指向全局對象,這甚至也可以歸結為語言層面的bug之一。ES6的箭頭函數優(yōu)化了這一點,它的內部沒有自己的this,這也就導致了this總是指向上一層的this,如果上一層還是箭頭函數,則繼續(xù)向上指,直到指向到有自己this的函數為止,并作為自己的this。
Ⅱ. ?? 箭頭函數不能用作構造函數,因為它沒有自己的this,無法實例化。
Ⅲ. ??也是因為箭頭函數沒有自己的this,所以箭頭函數 內也不存在arguments對象。(可以用擴展運算符代替)
b.?函數默認賦值。ES6之前,函數的形參是無法給默認值得,只能在函數內部通過變通方法實現。ES6以更簡潔更明確的方式進行函數默認賦值。
function?es6Fuc?(x,?y?=?'default')?{
????console.log(x,?y);
}
es6Fuc(4)?//?4,?default
2、升級部分:
ES6新增了雙冒號運算符,用來取代以往的bind,call,和apply。(瀏覽器暫不支持,Babel已經支持轉碼)
foo::bar;
//?等同于
bar.bind(foo);
foo::bar(...arguments);
//?等同于
bar.apply(foo,?arguments);
復制代碼
十、問:Symbol是什么,有什么作用?
答:Symbol是ES6引入的第七種原始數據類型(說法不準確,應該是第七種數據類型,Object不是原始數據類型之一,已更正),所有Symbol()生成的值都是獨一無二的,可以從根本上解決對象屬性太多導致屬性名沖突覆蓋的問題。對象中Symbol()屬性不能被for...in遍歷,但是也不是私有屬性。
十一、問:Set是什么,有什么作用?
答:Set是ES6引入的一種類似Array的新的數據結構,Set實例的成員類似于數組item成員,區(qū)別是Set實例的成員都是唯一,不重復的。這個特性可以輕松地實現數組去重。
十二、問:Map是什么,有什么作用?
答:Map是ES6引入的一種類似Object的新的數據結構,Map可以理解為是Object的超集,打破了以傳統(tǒng)鍵值對形式定義對象,對象的key不再局限于字符串,也可以是Object。可以更加全面的描述對象的屬性。
十三、問:Proxy是什么,有什么作用?
答:Proxy是ES6新增的一個構造函數,可以理解為JS語言的一個代理,用來改變JS默認的一些語言行為,包括攔截默認的get/set等底層方法,使得JS的使用自由度更高,可以最大限度的滿足開發(fā)者的需求。比如通過攔截對象的get/set方法,可以輕松地定制自己想要的key或者value。下面的例子可以看到,隨便定義一個myOwnObj的key,都可以變成自己想要的函數。
function?createMyOwnObj()?{
?//想把所有的key都變成函數,或者Promise,或者anything
?return?new?Proxy({},?{
??get(target,?propKey,?receiver)?{
???return?new?Promise((resolve,?reject)?=>?{
????setTimeout(()?=>?{
?????let?randomBoolean?=?Math.random()?>?0.5;
?????let?Message;
?????if?(randomBoolean)?{
??????Message?=?`你的${propKey}運氣不錯,成功了`;
??????resolve(Message);
?????}?else?{
??????Message?=?`你的${propKey}運氣不行,失敗了`;
??????reject(Message);
?????}
????},?1000);
???});
??}
?});
}
let?myOwnObj?=?createMyOwnObj();
myOwnObj.hahaha.then(result?=>?{
?console.log(result)?//你的hahaha運氣不錯,成功了
}).catch(error?=>?{
?console.log(error)?//你的hahaha運氣不行,失敗了
})
myOwnObj.wuwuwu.then(result?=>?{
?console.log(result)?//你的wuwuwu運氣不錯,成功了
}).catch(error?=>?{
?console.log(error)?//你的wuwuwu運氣不行,失敗了
})
十四、問:Reflect是什么,有什么作用?
答:Reflect是ES6引入的一個新的對象,他的主要作用有兩點,一是將原生的一些零散分布在Object、Function或者全局函數里的方法(如apply、delete、get、set等等),統(tǒng)一整合到Reflect上,這樣可以更加方便更加統(tǒng)一的管理一些原生API。其次就是因為Proxy可以改寫默認的原生API,如果一旦原生API別改寫可能就找不到了,所以Reflect也可以起到備份原生API的作用,使得即使原生API被改寫了之后,也可以在被改寫之后的API用上默認的API。
十五、問:Promise是什么,有什么作用?
答:Promise是ES6引入的一個新的對象,他的主要作用是用來解決JS異步機制里,回調機制產生的“回調地獄”。它并不是什么突破性的API,只是封裝了異步回調形式,使得異步回調可以寫的更加優(yōu)雅,可讀性更高,而且可以鏈式調用。
十六、問:Iterator是什么,有什么作用?(重要)
答:Iterator是ES6中一個很重要概念,它并不是對象,也不是任何一種數據類型。因為ES6新增了Set、Map類型,他們和Array、Object類型很像,Array、Object都是可以遍歷的,但是Set、Map都不能用for循環(huán)遍歷,解決這個問題有兩種方案,一種是為Set、Map單獨新增一個用來遍歷的API,另一種是為Set、Map、Array、Object新增一個統(tǒng)一的遍歷API,顯然,第二種更好,ES6也就順其自然的需要一種設計標準,來統(tǒng)一所有可遍歷類型的遍歷方式。Iterator正是這樣一種標準。或者說是一種規(guī)范理念。
就好像JavaScript是ECMAScript標準的一種具體實現一樣,Iterator標準的具體實現是Iterator遍歷器。Iterator標準規(guī)定,所有部署了key值為[Symbol.iterator],且[Symbol.iterator]的value是標準的Iterator接口函數(標準的Iterator接口函數: 該函數必須返回一個對象,且對象中包含next方法,且執(zhí)行next()能返回包含value/done屬性的Iterator對象)的對象,都稱之為可遍歷對象,next()后返回的Iterator對象也就是Iterator遍歷器。
//obj就是可遍歷的,因為它遵循了Iterator標準,且包含[Symbol.iterator]方法,方法函數也符合標準的Iterator接口規(guī)范。
//obj.[Symbol.iterator]()?就是Iterator遍歷器
let?obj?=?{
??data:?[?'hello',?'world'?],
??[Symbol.iterator]()?{
????const?self?=?this;
????let?index?=?0;
????return?{
??????next()?{
????????if?(index???????????return?{
????????????value:?self.data[index++],
????????????done:?false
??????????};
????????}?else?{
??????????return?{?value:?undefined,?done:?true?};
????????}
??????}
????};
??}
};
ES6給Set、Map、Array、String都加上了[Symbol.iterator]方法,且[Symbol.iterator]方法函數也符合標準的Iterator接口規(guī)范,所以Set、Map、Array、String默認都是可以遍歷的。
//Array
let?array?=?['red',?'green',?'blue'];
array[Symbol.iterator]()?//Iterator遍歷器
array[Symbol.iterator]().next()?//{value:?"red",?done:?false}
//String
let?string?=?'1122334455';
string[Symbol.iterator]()?//Iterator遍歷器
string[Symbol.iterator]().next()?//{value:?"1",?done:?false}
//set
let?set?=?new?Set(['red',?'green',?'blue']);
set[Symbol.iterator]()?//Iterator遍歷器
set[Symbol.iterator]().next()?//{value:?"red",?done:?false}
//Map
let?map?=?new?Map();
let?obj=?{map:?'map'};
map.set(obj,?'mapValue');
map[Symbol.iterator]().next()??{value:?Array(2),?done:?false}
十七、問:for...in 和for...of有什么區(qū)別?
答:如果看到問題十六,那么就很好回答。問題十六提到了ES6統(tǒng)一了遍歷標準,制定了可遍歷對象,那么用什么方法去遍歷呢?答案就是用for...of。ES6規(guī)定,有所部署了載了Iterator接口的對象(可遍歷對象)都可以通過for...of去遍歷,而for..in僅僅可以遍歷對象。
這也就意味著,數組也可以用for...of遍歷,這極大地方便了數組的取值,且避免了很多程序用for..in去遍歷數組的惡習。
上面提到的擴展運算符本質上也就是for..of循環(huán)的一種實現。
十八、Generator函數是什么,有什么作用?
答:如果說JavaScript是ECMAScript標準的一種具體實現、Iterator遍歷器是Iterator的具體實現,那么Generator函數可以說是Iterator接口的具體實現方式。
執(zhí)行Generator函數會返回一個遍歷器對象,每一次Generator函數里面的yield都相當一次遍歷器對象的next()方法,并且可以通過next(value)方法傳入自定義的value,來改變Generator函數的行為。
Generator函數可以通過配合Thunk 函數更輕松更優(yōu)雅的實現異步編程和控制流管理。
十九、async函數是什么,有什么作用?
答:async函數可以理解為內置自動執(zhí)行器的Generator函數語法糖,它配合ES6的Promise近乎完美的實現了異步編程解決方案。
附錄:手寫async await的最簡實現(20行搞定)!阿里字節(jié)面試必考
二十、Class、extends是什么,有什么作用?
答:ES6 的class可以看作只是一個ES5生成實例對象的構造函數的語法糖。它參考了java語言,定義了一個類的概念,讓對象原型寫法更加清晰,對象實例化更像是一種面向對象編程。Class類可以通過extends實現繼承。它和ES5構造函數的不同點:
a. 類的內部定義的所有方法,都是不可枚舉的。
///ES5
function?ES5Fun?(x,?y)?{
?this.x?=?x;
?this.y?=?y;
}
ES5Fun.prototype.toString?=?function?()?{
??return?'('?+?this.x?+?',?'?+?this.y?+?')';
}
var?p?=?new?ES5Fun(1,?3);
p.toString();
Object.keys(ES5Fun.prototype);?//['toString']
//ES6
class?ES6Fun?{
?constructor?(x,?y)?{
??this.x?=?x;
??this.y?=?y;
?}
?toString?()?{
??return?'('?+?this.x?+?',?'?+?this.y?+?')';
?}
}
Object.keys(ES6Fun.prototype);?//[]
b.ES6的class類必須用new命令操作,而ES5的構造函數不用new也可以執(zhí)行。
c.ES6的class類不存在變量提升,必須先定義class之后才能實例化,不像ES5中可以將構造函數寫在實例化之后。
d.ES5 的繼承,實質是先創(chuàng)造子類的實例對象this,然后再將父類的方法添加到this上面。ES6 的繼承機制完全不同,實質是先將父類實例對象的屬性和方法,加到this上面(所以必須先調用super方法),然后再用子類的構造函數修改this。
二十一、module、export、import是什么,有什么作用?
答:module、export、import是ES6用來統(tǒng)一前端模塊化方案的設計思路和實現方案。export、import的出現統(tǒng)一了前端模塊化的實現方案,整合規(guī)范了瀏覽器/服務端的模塊化方法,用來取代傳統(tǒng)的AMD/CMD、requireJS、seaJS、commondJS等等一系列前端模塊不同的實現方案,使前端模塊化更加統(tǒng)一規(guī)范,JS也能更加能實現大型的應用程序開發(fā)。
import引入的模塊是靜態(tài)加載(編譯階段加載)而不是動態(tài)加載(運行時加載)。
import引入export導出的接口值是動態(tài)綁定關系,即通過該接口,可以取到模塊內部實時的值。
二十二、日常前端代碼開發(fā)中,有哪些值得用ES6去改進的編程優(yōu)化或者規(guī)范?
答:
常用箭頭函數來取代 var self = this;的做法。常用let取代var命令。 常用數組/對象的結構賦值來命名變量,結構更清晰,語義更明確,可讀性更好。 在長字符串多變量組合場合,用模板字符串來取代字符串累加,能取得更好地效果和閱讀體驗。 用Class類取代傳統(tǒng)的構造函數,來生成實例化對象。 在大型應用開發(fā)中,要保持module模塊化開發(fā)思維,分清模塊之間的關系,常用import、export方法。
作者:StevenLikeWatermelon https://juejin.cn/post/6844903734464495623
The End
歡迎自薦投稿到《前端技術江湖》,如果你覺得這篇內容對你挺有啟發(fā),記得點個?「在看」哦
點個『在看』支持下?
