ES 基礎知識點與高頻考題解析
JavaScript 是 ECMAScript 規範的一種實現,本小節重點梳理下 ECMAScript 中的常考知識點,然後就一些容易出現的題目進行解析。
知識點梳理
- 變量類型
- JS 的數據類型分類和判斷
- 值類型和引用類型
- 原型與原型鏈(繼承)
- 原型和原型鏈定義
- 繼承寫法
- 作用域和閉包
- 執行上下文
- this
- 閉包是什麼
- 異步
- 同步 vs 異步
- 異步和單線程
- 前端異步的場景
- ES6/7 新標準的考查
- 箭頭函數
- Module
- Class
- Set 和 Map
- Promise
變量類型
JavaScript 是一種弱類型腳本語言,所謂弱類型指的是定義變量時,不需要什麼類型,在程序運行過程中會自動判斷類型。
ECMAScript 中定義了 6 種原始類型:
- Boolean
- String
- Number
- Null
- Undefined
- Symbol(ES6 新定義)
注意:原始類型不包含 Object。
題目:類型判斷用到哪些方法?
typeof
typeof xxx
得到的值有以下幾種類型:undefined
boolean
number
string
object
function
、symbol
,比較簡單,不再一一演示了。這裏需要注意的有三點:
typeof null
結果是object
,實際這是typeof
的一個bug,null是原始值,非引用類型typeof [1, 2]
結果是object
,結果中沒有array
這一項,引用類型除了function
其他的全部都是object
typeof Symbol()
用typeof
獲取symbol
類型的值得到的是symbol
,這是 ES6 新增的知識點
instanceof
用於實例和構造函數的對應。例如判斷一個變量是否是數組,使用typeof
無法判斷,但可以使用[1, 2] instanceof Array
來判斷。因爲,[1, 2]
是數組,它的構造函數就是Array
。同理:
function Foo(name) {
this.name = name
}
var foo = new Foo('bar')
console.log(foo instanceof Foo) // true
題目:值類型和引用類型的區別
值類型 vs 引用類型
除了原始類型,ES 還有引用類型,上文提到的typeof
識別出來的類型中,只有object
和function
是引用類型,其他都是值類型。
根據 JavaScript 中的變量類型傳遞方式,又分爲值類型和引用類型,值類型變量包括 Boolean、String、Number、Undefined、Null,引用類型包括了 Object 類的所有,如 Date、Array、Function 等。在參數傳遞方式上,值類型是按值傳遞,引用類型是按共享傳遞。
下面通過一個小題目,來看下兩者的主要區別,以及實際開發中需要注意的地方。
// 值類型
var a = 10
var b = a
b = 20
console.log(a) // 10
console.log(b) // 20
上述代碼中,a
b
都是值類型,兩者分別修改賦值,相互之間沒有任何影響。再看引用類型的例子:
// 引用類型
var a = {x: 10, y: 20}
var b = a
b.x = 100
b.y = 200
console.log(a) // {x: 100, y: 200}
console.log(b) // {x: 100, y: 200}
上述代碼中,a
b
都是引用類型。在執行了b = a
之後,修改b
的屬性值,a
的也跟着變化。因爲a
和b
都是引用類型,指向了同一個內存地址,即兩者引用的是同一個值,因此b
修改屬性時,a
的值隨之改動。
再借助題目進一步講解一下。
說出下面代碼的執行結果,並分析其原因。
function foo(a){
a = a * 10;
}
function bar(b){
b.value = 'new';
}
var a = 1;
var b = {value: 'old'};
foo(a);
bar(b);
console.log(a); // 1
console.log(b); // value: new
通過代碼執行,會發現:
a
的值沒有發生改變- 而
b
的值發生了改變
這就是因爲Number
類型的a
是按值傳遞的,而Object
類型的b
是按共享傳遞的。
JS 中這種設計的原因是:按值傳遞的類型,複製一份存入棧內存,這類類型一般不佔用太多內存,而且按值傳遞保證了其訪問速度。按共享傳遞的類型,是複製其引用,而不是整個複製其值(C 語言中的指針),保證過大的對象等不會因爲不停複製內容而造成內存的浪費。
引用類型經常會在代碼中按照下面的寫法使用,或者說容易不知不覺中造成錯誤!
var obj = {
a: 1,
b: [1,2,3]
}
var a = obj.a
var b = obj.b
a = 2
b.push(4)
console.log(obj, a, b)
雖然obj
本身是個引用類型的變量(對象),但是內部的a
和b
一個是值類型一個是引用類型,a
的賦值不會改變obj.a
,但是b
的操作卻會反映到obj
對象上。
原型和原型鏈
JavaScript 是基於原型的語言,原型理解起來非常簡單,但卻特別重要,下面還是通過題目來理解下JavaScript 的原型概念。
題目:如何理解 JavaScript 的原型
對於這個問題,可以從下面這幾個要點來理解和回答,下面幾條必須記住並且理解
- 所有的引用類型(數組、對象、函數),都具有對象特性,即可自由擴展屬性(
null
除外) - 所有的引用類型(數組、對象、函數),都有一個
__proto__
屬性,屬性值是一個普通的對象 - 所有的函數,都有一個
prototype
屬性,屬性值也是一個普通的對象 - 所有的引用類型(數組、對象、函數),
__proto__
屬性值指向它的構造函數的prototype
屬性值
通過代碼解釋一下,大家可自行運行以下代碼,看結果。
// 要點一:自由擴展屬性
var obj = {}; obj.a = 100;
var arr = []; arr.a = 100;
function fn () {}
fn.a = 100;
// 要點二:__proto__
console.log(obj.__proto__);
console.log(arr.__proto__);
console.log(fn.__proto__);
// 要點三:函數有 prototype
console.log(fn.prototype)
// 要點四:引用類型的 __proto__ 屬性值指向它的構造函數的 prototype 屬性值
console.log(obj.__proto__ === Object.prototype)
原型
先寫一個簡單的代碼示例。
// 構造函數
function Foo(name, age) {
this.name = name
}
Foo.prototype.alertName = function () {
alert(this.name)
}
// 創建示例
var f = new Foo('zhangsan')
f.printName = function () {
console.log(this.name)
}
// 測試
f.printName()
f.alertName()
執行printName
時很好理解,但是執行alertName
時發生了什麼?這裏再記住一個重點 當試圖得到一個對象的某個屬性時,如果這個對象本身沒有這個屬性,那麼會去它的__proto__
(即它的構造函數的prototype
)中尋找,因此f.alertName
就會找到Foo.prototype.alertName
。
那麼如何判斷這個屬性是不是對象本身的屬性呢?使用hasOwnProperty
,常用的地方是遍歷一個對象的時候。
var item
for (item in f) {
// 高級瀏覽器已經在 for in 中屏蔽了來自原型的屬性,但是這裏建議大家還是加上這個判斷,保證程序的健壯性
if (f.hasOwnProperty(item)) {
console.log(item)
}
}
題目:如何理解 JS 的原型鏈
原型鏈
還是接着上面的示例,如果執行f.toString()
時,又發生了什麼?
// 省略 N 行
// 測試
f.printName()
f.alertName()
f.toString()
因爲f
本身沒有toString()
,並且f.__proto__
(即Foo.prototype
)中也沒有toString
。這個問題還是得拿出剛纔那句話——當試圖得到一個對象的某個屬性時,如果這個對象本身沒有這個屬性,那麼會去它的__proto__
(即它的構造函數的prototype
)中尋找。
如果在f.__proto__
中沒有找到toString
,那麼就繼續去f.__proto__.__proto__
中尋找,因爲f.__proto__
就是一個普通的對象而已嘛!
f.__proto__
即Foo.prototype
,沒有找到toString
,繼續往上找f.__proto__.__proto__
即Foo.prototype.__proto__
。Foo.prototype
就是一個普通的對象,因此Foo.prototype.__proto__
就是Object.prototype
,在這裏可以找到toString
- 因此
f.toString
最終對應到了Object.prototype.toString
這樣一直往上找,你會發現是一個鏈式的結構,所以叫做“原型鏈”。如果一直找到最上層都沒有找到,那麼就宣告失敗,返回undefined
。最上層是什麼 —— Object.prototype.__proto__ === null
原型鏈中的this
所有從原型或更高級原型中得到、執行的方法,其中的this
在執行時,就指向了當前這個觸發事件執行的對象。因此printName
和alertName
中的this
都是f
。
作用域和閉包
作用域和閉包是前端面試中,最可能考查的知識點。例如下面的題目:
題目:現在有個 HTML 片段,要求編寫代碼,點擊編號爲幾的鏈接就
alert
彈出其編號
<ul>
<li>編號1,點擊我請彈出1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
一般不知道這個題目用閉包的話,會寫出下面的代碼:
var list = document.getElementsByTagName('li');
for (var i = 0; i < list.length; i++) {
list[i].addEventListener('click', function(){
alert(i + 1)
}, true)
}
實際上執行纔會發現始終彈出的是6
,這時候就應該通過閉包來解決:
var list = document.getElementsByTagName('li');
for (var i = 0; i < list.length; i++) {
list[i].addEventListener('click', function(i){
return function(){
alert(i + 1)
}
}(i), true)
}
要理解閉包,就需要我們從「執行上下文」開始講起。
執行上下文
先講一個關於 變量提升 的知識點,面試中可能會遇見下面的問題,很多候選人都回答錯誤:
題目:說出下面執行的結果(這裏筆者直接註釋輸出了)
console.log(a) // undefined
var a = 100
fn('zhangsan') // 'zhangsan' 20
function fn(name) {
age = 20
console.log(name, age)
var age
}
console.log(b); // 這裏報錯
// Uncaught ReferenceError: b is not defined
b = 100;
在一段 JS 腳本(即一個<script>
標籤中)執行之前,要先解析代碼(所以說 JS 是解釋執行的腳本語言),解析的時候會先創建一個 全局執行上下文 環境,先把代碼中即將執行的(內部函數的不算,因爲你不知道函數何時執行)變量、函數聲明都拿出來。變量先暫時賦值爲undefined
,函數則先聲明好可使用。這一步做完了,然後再開始正式執行程序。再次強調,這是在代碼執行之前纔開始的工作。
我們來看下上面的面試小題目,爲什麼a
是undefined
,而b
卻報錯了,實際 JS 在代碼執行之前,要「全文解析」,發現var a
,知道有個a
的變量,存入了執行上下文,而b
沒有找到var
關鍵字,這時候沒有在執行上下文提前「佔位」,所以代碼執行的時候,提前報到的a
是有記錄的,只不過值暫時還沒有賦值,即爲undefined
,而b
在執行上下文沒有找到,自然會報錯(沒有找到b
的引用)。
另外,一個函數在執行之前,也會創建一個 函數執行上下文 環境,跟 全局上下文 差不多,不過 函數執行上下文 中會多出this
arguments
和函數的參數。參數和arguments
好理解,這裏的this
咱們需要專門講解。
總結一下:
- 範圍:一段
<script>
、js 文件或者一個函數 - 全局上下文:變量定義,函數聲明
- 函數上下文:變量定義,函數聲明,
this
,arguments
this
先搞明白一個很重要的概念 —— this
的值是在執行的時候才能確認,定義的時候不能確認! 爲什麼呢 —— 因爲this
是執行上下文環境的一部分,而執行上下文需要在代碼執行之前確定,而不是定義的時候。看如下例子
var a = {
name: 'A',
fn: function () {
console.log(this.name)
}
}
a.fn() // this === a
a.fn.call({name: 'B'}) // this === {name: 'B'}
var fn1 = a.fn
fn1() // this === window
this
執行會有不同,主要集中在這幾個場景中
- 作爲構造函數執行,構造函數中
- 作爲對象屬性執行,上述代碼中
a.fn()
- 作爲普通函數執行,上述代碼中
fn1()
- 用於
call
apply
bind
,上述代碼中a.fn.call({name: 'B'})
下面再來講解下什麼是作用域和作用域鏈,作用域鏈和作用域也是常考的題目。
題目:如何理解 JS 的作用域和作用域鏈
作用域
ES6 之前 JS 沒有塊級作用域。例如
if (true) {
var name = 'zhangsan'
}
console.log(name)
從上面的例子可以體會到作用域的概念,作用域就是一個獨立的地盤,讓變量不會外泄、暴露出去。上面的name
就被暴露出去了,因此,JS 沒有塊級作用域,只有全局作用域和函數作用域。
var a = 100
function fn() {
var a = 200
console.log('fn', a)
}
console.log('global', a)
fn()
全局作用域就是最外層的作用域,如果我們寫了很多行 JS 代碼,變量定義都沒有用函數包括,那麼它們就全部都在全局作用域中。這樣的壞處就是很容易撞車、衝突。
// 張三寫的代碼中
var data = {a: 100}
// 李四寫的代碼中
var data = {x: true}
這就是爲何 jQuery、Zepto 等庫的源碼,所有的代碼都會放在(function(){....})()
中。因爲放在裏面的所有變量,都不會被外泄和暴露,不會污染到外面,不會對其他的庫或者 JS 腳本造成影響。這是函數作用域的一個體現。
附:ES6 中開始加入了塊級作用域,使用let
定義變量即可,如下:
if (true) {
let name = 'zhangsan'
}
console.log(name) // 報錯,因爲let定義的name是在if這個塊級作用域
作用域鏈
首先認識一下什麼叫做 自由變量 。如下代碼中,console.log(a)
要得到a
變量,但是在當前的作用域中沒有定義a
(可對比一下b
)。當前作用域沒有定義的變量,這成爲 自由變量 。自由變量如何得到 —— 向父級作用域尋找。
var a = 100
function fn() {
var b = 200
console.log(a)
console.log(b)
}
fn()
如果父級也沒呢?再一層一層向上尋找,直到找到全局作用域還是沒找到,就宣佈放棄。這種一層一層的關係,就是 作用域鏈 。
var a = 100
function F1() {
var b = 200
function F2() {
var c = 300
console.log(a) // 自由變量,順作用域鏈向父作用域找
console.log(b) // 自由變量,順作用域鏈向父作用域找
console.log(c) // 本作用域的變量
}
F2()
}
F1()
閉包
講完這些內容,我們再來看一個例子,通過例子來理解閉包。
function F1() {
var a = 100
return function () {
console.log(a)
}
}
var f1 = F1()
var a = 200
f1()
自由變量將從作用域鏈中去尋找,但是 依據的是函數定義時的作用域鏈,而不是函數執行時,以上這個例子就是閉包。閉包主要有兩個應用場景:
- 函數作爲返回值,上面的例子就是
- 函數作爲參數傳遞,看以下例子
function F1() {
var a = 100
return function () {
console.log(a)
}
}
function F2(f1) {
var a = 200
console.log(f1())
}
var f1 = F1()
F2(f1)
至此,對應着「作用域和閉包」這部分一開始的點擊彈出alert
的代碼再看閉包,就很好理解了。
異步
異步和同步也是面試中常考的內容,下面筆者來講解下同步和異步的區別。
同步 vs 異步
先看下面的 demo,根據程序閱讀起來表達的意思,應該是先打印100
,1秒鐘之後打印200
,最後打印300
。但是實際運行根本不是那麼回事。
console.log(100)
setTimeout(function () {
console.log(200)
}, 1000)
console.log(300)
再對比以下程序。先打印100
,再彈出200
(等待用戶確認),最後打印300
。這個運行效果就符合預期要求。
console.log(100)
alert(200) // 1秒鐘之後點擊確認
console.log(300)
這倆到底有何區別?—— 第一個示例中間的步驟根本沒有阻塞接下來程序的運行,而第二個示例卻阻塞了後面程序的運行。前面這種表現就叫做 異步(後面這個叫做 同步 ),即不會阻塞後面程序的運行。
異步和單線程
JS 需要異步的根本原因是 JS 是單線程運行的,即在同一時間只能做一件事,不能“一心二用”。
一個 Ajax 請求由於網絡比較慢,請求需要 5 秒鐘。如果是同步,這 5 秒鐘頁面就卡死在這裏啥也幹不了了。異步的話,就好很多了,5 秒等待就等待了,其他事情不耽誤做,至於那 5 秒鐘等待是網速太慢,不是因爲 JS 的原因。
講到單線程,我們再來看個真題:
題目:講解下面代碼的執行過程和結果
var a = true;
setTimeout(function(){
a = false;
}, 100)
while(a){
console.log('while執行了')
}
這是一個很有迷惑性的題目,不少候選人認爲100ms
之後,由於a
變成了false
,所以while
就中止了,實際不是這樣,因爲JS是單線程的,所以進入while
循環之後,沒有「時間」(線程)去跑定時器了,所以這個代碼跑起來是個死循環!
前端異步的場景
- 定時
setTimeout
setInterval
- 網絡請求,如
Ajax
<img>
加載
Ajax 代碼示例
console.log('start')
$.get('./data1.json', function (data1) {
console.log(data1)
})
console.log('end')
img 代碼示例(常用於打點統計)
console.log('start')
var img = document.createElement('img')
// 或者 img = new Image()
img.onload = function () {
console.log('loaded')
img.onload = null
}
img.src = '/xxx.png'
console.log('end')
ES6/7 新標準的考查
題目:ES6 箭頭函數中的
this
和普通函數中的有什麼不同
箭頭函數
箭頭函數是 ES6 中新的函數定義形式,function name(arg1, arg2) {...}
可以使用(arg1, arg2) => {...}
來定義。示例如下:
// JS 普通函數
var arr = [1, 2, 3]
arr.map(function (item) {
console.log(index)
return item + 1
})
// ES6 箭頭函數
const arr = [1, 2, 3]
arr.map((item, index) => {
console.log(index)
return item + 1
})
箭頭函數存在的意義,第一寫起來更加簡潔,第二可以解決 ES6 之前函數執行中this
是全局變量的問題,看如下代碼
function fn() {
console.log('real', this) // {a: 100} ,該作用域下的 this 的真實的值
var arr = [1, 2, 3]
// 普通 JS
arr.map(function (item) {
console.log('js', this) // window 。普通函數,這裏打印出來的是全局變量,令人費解
return item + 1
})
// 箭頭函數
arr.map(item => {
console.log('es6', this) // {a: 100} 。箭頭函數,這裏打印的就是父作用域的 this
return item + 1
})
}
fn.call({a: 100})
題目:ES6 模塊化如何使用?
Module
ES6 中模塊化語法更加簡潔,直接看示例。
如果只是輸出一個唯一的對象,使用export default
即可,代碼如下
// 創建 util1.js 文件,內容如
export default {
a: 100
}
// 創建 index.js 文件,內容如
import obj from './util1.js'
console.log(obj)
如果想要輸出許多個對象,就不能用default
了,且import
時候要加{...}
,代碼如下
// 創建 util2.js 文件,內容如
export function fn1() {
alert('fn1')
}
export function fn2() {
alert('fn2')
}
// 創建 index.js 文件,內容如
import { fn1, fn2 } from './util2.js'
fn1()
fn2()
題目:ES6 class 和普通構造函數的區別
class
class 其實一直是 JS 的關鍵字(保留字),但是一直沒有正式使用,直到 ES6 。 ES6 的 class 就是取代之前構造函數初始化對象的形式,從語法上更加符合面向對象的寫法。例如:
JS 構造函數的寫法
function MathHandle(x, y) {
this.x = x;
this.y = y;
}
MathHandle.prototype.add = function () {
return this.x + this.y;
};
var m = new MathHandle(1, 2);
console.log(m.add())
用 ES6 class 的寫法
class MathHandle {
constructor(x, y) {
this.x = x;
this.y = y;
}
add() {
return this.x + this.y;
}
}
const m = new MathHandle(1, 2);
console.log(m.add())
注意以下幾點,全都是關於 class 語法的:
- class 是一種新的語法形式,是
class Name {...}
這種形式,和函數的寫法完全不一樣 - 兩者對比,構造函數函數體的內容要放在 class 中的
constructor
函數中,constructor
即構造器,初始化實例時默認執行 - class 中函數的寫法是
add() {...}
這種形式,並沒有function
關鍵字
使用 class 來實現繼承就更加簡單了,至少比構造函數實現繼承簡單很多。看下面例子
JS 構造函數實現繼承
// 動物
function Animal() {
this.eat = function () {
console.log('animal eat')
}
}
// 狗
function Dog() {
this.bark = function () {
console.log('dog bark')
}
}
Dog.prototype = new Animal()
// 哈士奇
var hashiqi = new Dog()
ES6 class 實現繼承
class Animal {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name} eat`)
}
}
class Dog extends Animal {
constructor(name) {
super(name)
this.name = name
}
say() {
console.log(`${this.name} say`)
}
}
const dog = new Dog('哈士奇')
dog.say()
dog.eat()
注意以下兩點:
- 使用
extends
即可實現繼承,更加符合經典面嚮對象語言的寫法,如 Java - 子類的
constructor
一定要執行super()
,以調用父類的constructor
題目:ES6 中新增的數據類型有哪些?
Set 和 Map
Set 和 Map 都是 ES6 中新增的數據結構,是對當前 JS 數組和對象這兩種重要數據結構的擴展。由於是新增的數據結構,目前尚未被大規模使用,但是作爲前端程序員,提前瞭解是必須做到的。先總結一下兩者最關鍵的地方:
- Set 類似於數組,但數組可以允許元素重複,Set 不允許元素重複
- Map 類似於對象,但普通對象的 key 必須是字符串或者數字,而 Map 的 key 可以是任何數據類型
Set
Set 實例不允許元素有重複,可以通過以下示例證明。可以通過一個數組初始化一個 Set 實例,或者通過add
添加元素,元素不能重複,重複的會被忽略。
// 例1
const set = new Set([1, 2, 3, 4, 4]);
console.log(set) // Set(4) {1, 2, 3, 4}
// 例2
const set = new Set();
[2, 3, 5, 4, 5, 8, 8].forEach(item => set.add(item));
for (let item of set) {
console.log(item);
}
// 2 3 5 4 8
Set 實例的屬性和方法有
size
:獲取元素數量。add(value)
:添加元素,返回 Set 實例本身。delete(value)
:刪除元素,返回一個布爾值,表示刪除是否成功。has(value)
:返回一個布爾值,表示該值是否是 Set 實例的元素。clear()
:清除所有元素,沒有返回值。
const s = new Set();
s.add(1).add(2).add(2); // 添加元素
s.size // 2
s.has(1) // true
s.has(2) // true
s.has(3) // false
s.delete(2);
s.has(2) // false
s.clear();
console.log(s); // Set(0) {}
Set 實例的遍歷,可使用如下方法
keys()
:返回鍵名的遍歷器。values()
:返回鍵值的遍歷器。不過由於 Set 結構沒有鍵名,只有鍵值(或者說鍵名和鍵值是同一個值),所以keys()
和values()
返回結果一致。entries()
:返回鍵值對的遍歷器。forEach()
:使用回調函數遍歷每個成員。
let set = new Set(['aaa', 'bbb', 'ccc']);
for (let item of set.keys()) {
console.log(item);
}
// aaa
// bbb
// ccc
for (let item of set.values()) {
console.log(item);
}
// aaa
// bbb
// ccc
for (let item of set.entries()) {
console.log(item);
}
// ["aaa", "aaa"]
// ["bbb", "bbb"]
// ["ccc", "ccc"]
set.forEach((value, key) => console.log(key + ' : ' + value))
// aaa : aaa
// bbb : bbb
// ccc : ccc
Map
Map 的用法和普通對象基本一致,先看一下它能用非字符串或者數字作爲 key 的特性。
const map = new Map();
const obj = {p: 'Hello World'};
map.set(obj, 'OK')
map.get(obj) // "OK"
map.has(obj) // true
map.delete(obj) // true
map.has(obj) // false
需要使用new Map()
初始化一個實例,下面代碼中set
get
has
delete
顧名即可思義(下文也會演示)。其中,map.set(obj, 'OK')
就是用對象作爲的 key (不光可以是對象,任何數據類型都可以),並且後面通過map.get(obj)
正確獲取了。
Map 實例的屬性和方法如下:
size
:獲取成員的數量set
:設置成員 key 和 valueget
:獲取成員屬性值has
:判斷成員是否存在delete
:刪除成員clear
:清空所有
const map = new Map();
map.set('aaa', 100);
map.set('bbb', 200);
map.size // 2
map.get('aaa') // 100
map.has('aaa') // true
map.delete('aaa')
map.has('aaa') // false
map.clear()
Map 實例的遍歷方法有:
keys()
:返回鍵名的遍歷器。values()
:返回鍵值的遍歷器。entries()
:返回所有成員的遍歷器。forEach()
:遍歷 Map 的所有成員。
const map = new Map();
map.set('aaa', 100);
map.set('bbb', 200);
for (let key of map.keys()) {
console.log(key);
}
// "aaa"
// "bbb"
for (let value of map.values()) {
console.log(value);
}
// 100
// 200
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
// aaa 100
// bbb 200
// 或者
for (let [key, value] of map.entries()) {
console.log(key, value);
}
// aaa 100
// bbb 200
Promise
Promise
是 CommonJS 提出來的這一種規範,有多個版本,在 ES6 當中已經納入規範,原生支持 Promise 對象,非 ES6 環境可以用類似 Bluebird、Q 這類庫來支持。
Promise
可以將回調變成鏈式調用寫法,流程更加清晰,代碼更加優雅。
簡單歸納下 Promise:三個狀態、兩個過程、一個方法,快速記憶方法:3-2-1
三個狀態:pending
、fulfilled
、rejected
兩個過程:
- pending→fulfilled(resolve)
- pending→rejected(reject)
一個方法:then
當然還有其他概念,如catch
、 Promise.all/race
,這裏就不展開了。
關於 ES6/7 的考查內容還有很多,本小節就不逐一介紹了,如果想繼續深入學習,可以在線看《ES6入門》。
小結
本小節主要總結了 ES 基礎語法中面試經常考查的知識點,包括之前就考查較多的原型、異步、作用域,以及 ES6 的一些新內容,這些知識點希望大家都要掌握。