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