更新時間:2022-12-14 16:24:37 來源:動力節點 瀏覽1018次
這是一道非常經典的面試題,涵蓋了從函數的基本概念、運算符優先級,到作用域鏈、原型鏈、this關鍵字、new關鍵字等基礎知識點考察,可以說能完整答對 JS 基礎才算過了關,本文就帶大家一起回顧這道面試題,徹底搞懂它。
1、前端面試題庫 (面試必備)
// a
function Foo () {
getName = function () {
console.log(1);
}
return this;
}
// b
Foo.getName = function () {
console.log(2);
}
// c
Foo.prototype.getName = function () {
console.log(3);
}
// d
var getName = function () {
console.log(4);
}
// e
function getName () {
console.log(5);
}
按順序執行后分別輸出什么?
先自己嘗試寫出結果再看答案,后面是詳細解析。
答案:
Foo.getName(); // 2
getName(); // 4
Foo().getName(); // 1
getName(); // 1
new Foo.getName(); // 2
new Foo().getName(); // 3
new new Foo().getName(); // 3
解析
1. Foo.getName()
這一問首先考察的是函數的基本概念:在 JS 中函數是第一類對象,也被稱作"一等公民",這是因為函數擁有對象所擁有的全部功能。所以這里的 ??Foo.getName()??? 可以看作是調用了 ??Foo?? 對象上的屬性,在題目中的 b 處有其定義,故結果輸出 2 。
2. getName()
這里調用的 ??getName?? 在上下文中被定義了兩次,一次是通過變量聲明,一次是函數聲明,故這一問考察的是變量聲明提升與函數聲明提升,聲明提前會讓聲明提升到代碼的最上層,而函數再一次發揮了它"一等公民"的特權:函數聲明提升比變量更高,所以這一問實際執行代碼可看作:
function getName() {
console.log(5);
};
var getName;
getName = function () {
console.log(4);
};
兩者聲明共同提升之后,變量的賦值操作最后執行,所以調用 ??getName()?? 輸出的結果是 4 。
3. Foo().getName()
和第一問相比看似只多了個括號,實際考察的內容完全不一樣。
我們先復習一下 JS 中的運算符優先級,這是下來全部解題的基礎,MDN 匯總表 -> 鏈接在這里。
首先成員訪問運算從左到右執行,所以我們要先看 ??Foo()?? 函數做了什么,根據題目 a 處的定義:
function Foo () {
getName = function () {
console.log(1);
}
return this;
}
執行 ??Foo()??? 之后為 ??getName??? 賦值一個函數,注意這里的 ??getName??? 并沒有 ??var?? 關鍵字,所以還考察了作用域鏈的知識點,JS 在遇到未聲明的變量時會向上一級一層層查找,前面我們知道了變量聲明會提升,在全局作用域下 ??getName??? 是已經被聲明的了,所以執行 ??Foo()??? 的作用其實就是把全局的 ??getName?? 又賦值了新函數。
而 ??Foo()??? 本身返回了 ??this???,所以這一問又變成了「??this.getName()?? 輸出什么?」。這里當然也就考察了 this 關鍵字 的知識點,只要記住:this 誰最后調用它那它就指向誰,這里的 ??this??? 沒有改變過指向,所以是在全局下執行,也就是執行 ??getName()???,執行結果是前面 ??Foo()?? 賦予的新函數,所以輸出了 1 。
4. getName()
由于題目條件是順序執行,所以這里經過了第三問之后全局 ??getName?? 已經被修改過了,在上一問已經解析完,這里毫無疑問執行輸出是 1 。
5. new Foo.getName()
乍一看以為是要考察new 關鍵字 了,其實并沒有,它還是考察了上面提到的運算符優先級,根據優先級我們可以得出,??Foo.getName()??? 是會先執行的,執行完只是輸出了第一問的結果,再對其執行 ??new?? 沒有意義,最后輸出的還是 2 。
6. new Foo().getName()
這里開始考察 new 關鍵字 的概念,但我們還是要先說說這一問涉及的運算符優先級問題,可能你看過其它文章解析這一問的時候會說等價于 ??(new Foo()).getName()???,可你知道為什么會是這樣嗎?為什么第 5 問是先執行 ??Foo.getName()??? 而這一問卻是先執行 ??new Foo()?? 呢?
這是因為 ??new??? 運算在優先級上有兩種形式,一種是帶參數列表: ??new … ( … )?? 優先級 18,另一種是無參數列表: ??new …?? 優先級 17,如果優先級不同那么按優先級最高的運算符先執行,不用考慮結合性(比如 ??1 + 1 * 2??? 執行起來就是 ??1 + (1 * 2)???),如果優先級相同則按結合性執行(比如賦值運算結合性是"從右到左",所以 ??a = b = 1??? 實際為 ??a = (b = 1)???),所以這就解釋了為什么這一問會是 ??new Foo()?? 先執行,畫個圖就理解了:
在上一問里成員訪問優先級是18,??new???(無參列表)優先級是17,優先級不同,則高優先級先執行,所以上一問先執行 ??Foo.getName()???;這一問里 ??new???(帶參列表)優先級與成員訪問同屬18,優先級相同,并行下看結合性,??new??? 帶參時結合性不相關,所以直接執行,成員訪問結合性從左到右,所以先拿出 ??Foo()??? 執行,于是得出了上面等價于 ??(new Foo()).getName()?? 的結論。
接下來就是 new 的相關概念了,首先我們要知道 ??new?? 關鍵字做了什么:
創建新對象并將??.__proto__??? 指向構造函數的??.prototype??
將??this?? 指向新創建的對象
返回新對象
回到題目當中,??new Foo()??? 以 ??Foo??? 為原型創建了一個新對象,這個實例本身并沒有 ??geiName?? 這個方法,但是題目 c 處在 ??Foo??? 函數的原型上掛載過一個 ??getName?? 方法,最終實例會通過原型鏈訪問到 ??Foo.prototype.getName()?? 這個方法,結果輸出 3 。
原型鏈知識點:每個函數實例對象都有一個 ??__proto__??? 屬性,??__proto__??? 指向了 ??prototype???,當訪問實例對象的屬性或方法,會先從自身構造函數中查找,如果找不到就通過 ??__proto__?? 去原型中查找。
以上就是“JS經典面試題,考官必問考題”,你能回答上來嗎?如果想要了解更多的Java面試題相關內容,可以關注動力節點Java官網。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習