去年面試的時候,五位面試官有三位問到了這個問題,可見這是一個面試常題,我都忘記自己是怎麼回答的,要我現在說:箭頭函式沒有 this 繫結,它的 this 指向父作用域
果然,記憶記不牢是有原因的,因為沒有寫文章,沒有理解真正理解它
真正的答案是什麼?
阮一峰版:
- 箭頭函式沒有自己的 this 物件,函式體內的 this 是定義時所在的物件而不是使用時所在的物件
- 不可以當作建構函式,也就是說,不可以對箭頭函式使用 new 命令,否則會丟擲一個錯誤
- 不可以使用 arguments 物件,該物件在函式體內不存在。如果要用,可以用 rest 引數代替
- 不可以使用 yield 命令,因此箭頭函式不能用作 Generator 函式
- 返回物件時必須在物件外面加上括號
- 沒有 this、super、arguments 和 new.target 繫結。this、super、arguments 和 new.target 的值由最近的不包含箭頭函式的作用域決定
- 不能被 new 呼叫,箭頭函式內部沒有 [[Construct]] 方法,因此不能當作建構函式使用,使用 new 呼叫箭頭函式會丟擲錯誤
- 沒有 prototype,既然你不能使用 new 呼叫箭頭函式,那麼 prototype 就沒有存在的理由。箭頭函式沒有 prototype 屬性
- 不能更改 this, this 的值在函式內部不能被修改。在函式的整個生命週期內 this 的值是永恆不變的
- 沒有 arguments 物件,既然箭頭函式沒有 arguments 繫結,你必須依賴於命名或者剩餘引數來訪問該函式的引數
- 不允許重複的命名引數
尼古拉斯是寫《深入理解 ES6》的作者,阮一峰就不解釋了
結合起來,就是說箭頭函式和普通函式的區別在於:
- 它不能被當作建構函式,因為它不能被new,不能被 new 的原因在於箭頭函式內部沒有 [[Construct]] 方法。又因為它不能被 new,所以也就沒有 prototype
- 它沒有自己的 this,它的 this 由定義時所在的物件決定而不是使用時所在的物件
- 它也沒有 arguments 物件
- 不可以使用 yield 命令,不能用作生成器函式
我們依次說說這四點
new 從何來
先複習一下 new 呼叫建構函式會執行什麼
- 在記憶體中建立一個新物件
- 這個新物件內部的 [[prototype]] 特性被賦值為建構函式的 prototype 屬性
- 建構函式內部的 this 被賦值為這個新物件(this指向新物件)
- 執行建構函式內部的程式碼(給新物件新增屬性)
- 如果建構函式返回非空物件,則返回該物件;否則,返回剛建立的新物件
我們可以手寫一個 new
function new2(Constructor, ...args) {
var obj = Object.create(null);
obj.__proto__ = Constructor.prototype;
var result = Constructor.apply(obj, ...args)
return typeof result === 'object' ? result : obj
}
複習完 new,回過頭看為什麼不能呼叫 new
JavaScript 函式內部有兩個內部方法:[[Call]] 和 [[Construct]]
- 直接呼叫時執行[[Call]] 方法,直接執行函式體
- new 呼叫時執行 [[Construct]] 方法,建立例項物件
箭頭函式設計之初是為了設計一種更簡短的函式,沒有 [[Construct]] 方法。具體99.9%的人都不知道的箭頭函式不能當做建構函式的祕密 摘出了很多英文材料佐證這個事實
我們可以這樣說,因為它沒有[[Construct]] 內部方法,所以它不能被 new。而因為它不能被 new,所以它也沒有 prototype
prototype 的理解可以看這篇: 原型
this 誰人呼叫你
JavaScript 中的 this 是詞法作用域,與你在哪裡定義無關,而與你在哪裡呼叫有關,所以會有各種 this “妖”的問題,改變 this 有 4 種方法
- 作為物件方法呼叫
- 作為函式呼叫
- 作為建構函式呼叫
- 使用 apply 或 call 呼叫
但是箭頭函式沒有自己的 this 物件,內部的 this 就是定義時上層作用域中的this。也就是說,箭頭函式內部的 this 指向是固定的
arguments 老一輩的類陣列
arguments 是一個對應於傳遞給函式的引數的類陣列物件。arguments 物件標識所有(非箭頭)函式可用的區域性變數,可以說只要是(非箭頭)函式就自帶 arguments,它表示所有傳遞給函式的引數
什麼是類陣列物件
所謂類陣列物件,就是指可以通過索引屬性訪問元素並且擁有 length 屬性的物件
var arrLike = {
0: 'name',
1: 'age',
length: 2
}
箭頭函式沒有
yield 是什麼
說 yield 之前,先了解下生成器
生成器是 ES6 新增的一個極為靈活的結構,擁有在一個函式塊內暫停和恢復程式碼執行的能力。
生成器的形式是一個函式,函式名稱前面加一個星號(*)表示它是一個生成器。只要是可以定義(非箭頭)函式的地方,就可以定義生成器
// 生成器函式宣告
function* generatorFn() {}
// 生成器函式表示式
let generatorFn = function* () {}
// 作為物件字面量方法的生成器函式
let foo = {
* generatorFn() {}
}
// 作為類例項方法的生成器函式
class Foo {
* generatorFn() {}
}
// 作為類靜態方法的生成器函式
class Bar {
static * generatorFn() {}
}
標識生成器函式的星號不受兩側空格的影響
而 yield 關鍵字是可以讓生成器停止和開始執行,也是生成器最有用的地方。生成器函式在遇到 yield 關鍵字之前會正常執行。遇到這個關鍵字後,執行會停止,函式作用域的狀態會被保留。停止執行的生成器函式只能通過在生成器物件上呼叫 next() 方法來恢復執行
// umi 專案中請求介面時的例子
*fetchData({ payload }, { call, put }) {
const resData = yield call(fetchApi, payload);
if (resData.code === 'OK') {
yield put({
type: 'save',
payload: {
data: resData,
},
});
} else {
Toast.show(resData.resultMsg);
}
},
因為箭頭函式不能用來定義生成器函式才不能使用 yield 關鍵字
模擬面試
面試官:對 ES6 瞭解嗎
面試者:嗯呢,專案中一直有用
面試官:你說說你平時都用哪些 ES6 的新特性
面試者:例如箭頭函式、let、const、模板字串、擴充套件運算子、Promise...
面試官:嗯嗯,箭頭函式和普通函式有什麼區別
面試者:箭頭函式不能被 new、沒有 arguments、它的 this 與在那裡定義相關、它不能用 yield 命令,返回物件時必須在物件外面加上括號
面試官:箭頭函式為什麼不能被 new
面試者:因為箭頭函式沒有 [[Construct]] 方法,在 new 時,JavaScript 內部會呼叫 [[Construct]] 方法,因為箭頭函式沒有,所以 new 時會報錯。當然,因為不能被 new ,所以箭頭函式也沒有 prototype
面試官:你剛剛說到沒有 arguments,簡單介紹下它
面試者:它是所有引數的合集,每個(非箭頭)函式自帶 arguments,其結構是類陣列物件
面試官:什麼是類陣列物件
面試者:可以通過索引訪問元素且擁有 length 屬性的物件...
面試官:我問問其他的
參考資料
本文參與了 SegmentFault 思否徵文「如何“反殺”面試官?」,歡迎正在閱讀的你也加入。