前端面試中經(jīng)常忽視的一個(gè) JavaScript 面試題 (經(jīng)典易錯(cuò))

作者:Wscats
鏈接:https://github.com/Wscats/articles/issues/85

題目

function Foo() {
    getName = function () { alert (1); };
    return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

//請(qǐng)寫(xiě)出以下輸出結(jié)果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

這道題的經(jīng)典之處在于它綜合考察了面試者的JavaScript的綜合能力熬苍,包含了變量定義提升、this指針指向、運(yùn)算符優(yōu)先級(jí)柴底、原型婿脸、繼承、全局變量污染柄驻、對(duì)象屬性及原型屬性優(yōu)先級(jí)等知識(shí)狐树,此題在網(wǎng)上也有部分相關(guān)的解釋?zhuān)?dāng)然我覺(jué)得有部分解釋還欠妥,不夠清晰鸿脓,特地重頭到尾來(lái)分析一次吩愧,當(dāng)然我們會(huì)把最終答案放在后面,并把此題再改高一點(diǎn)點(diǎn)難度寺枉,改進(jìn)版也放在最后眨唬,方便面試官在出題的時(shí)候有個(gè)參考,


image.png

第一問(wèn)

先看此題的上半部分做了什么拨黔,首先定義了一個(gè)叫Foo的函數(shù)蛔溃,之后為Foo創(chuàng)建了一個(gè)叫g(shù)etName的靜態(tài)屬性存儲(chǔ)了一個(gè)匿名函數(shù),之后為Foo的原型對(duì)象新創(chuàng)建了一個(gè)叫g(shù)etName的匿名函數(shù)篱蝇。之后又通過(guò)函數(shù)變量表達(dá)式創(chuàng)建了一個(gè)getName的函數(shù)贺待,最后再聲明一個(gè)叫g(shù)etName函數(shù)。

第一問(wèn)的Foo.getName自然是訪問(wèn)Foo函數(shù)上存儲(chǔ)的靜態(tài)屬性零截,答案自然是2麸塞,這里就不需要解釋太多的,一般來(lái)說(shuō)第一問(wèn)對(duì)于稍微懂JS基礎(chǔ)的同學(xué)來(lái)說(shuō)應(yīng)該是沒(méi)問(wèn)題的,當(dāng)然我們可以用下面的代碼來(lái)回顧一下基礎(chǔ)瞻润,先加深一下了解

function User(name) {
    var name = name; //私有屬性
    this.name = name; //公有屬性
    function getName() { //私有方法
        return name;
    }
}
User.prototype.getName = function() { //公有方法
    return this.name;
}
User.name = 'Wscats'; //靜態(tài)屬性
User.getName = function() { //靜態(tài)方法
    return this.name;
}
var Wscat = new User('Wscats'); //實(shí)例化

注意下面這幾點(diǎn):

調(diào)用公有方法喘垂,公有屬性,我們必需先實(shí)例化對(duì)象绍撞,也就是用new操作符實(shí)化對(duì)象正勒,就可構(gòu)造函數(shù)實(shí)例化對(duì)象的方法和屬性,并且公有方法是不能調(diào)用私有方法和靜態(tài)方法的
靜態(tài)方法和靜態(tài)屬性就是我們無(wú)需實(shí)例化就可以調(diào)用
而對(duì)象的私有方法和屬性,外部是不可以訪問(wèn)的

第二問(wèn)

第二問(wèn)傻铣,直接調(diào)用getName函數(shù)章贞。既然是直接調(diào)用那么就是訪問(wèn)當(dāng)前上文作用域內(nèi)的叫g(shù)etName的函數(shù),所以這里應(yīng)該直接把關(guān)注點(diǎn)放在4和5上非洲,跟1 2 3都沒(méi)什么關(guān)系鸭限。當(dāng)然后來(lái)我問(wèn)了我的幾個(gè)同事他們大多數(shù)回答了5。此處其實(shí)有兩個(gè)坑两踏,一是變量聲明提升败京,二是函數(shù)表達(dá)式和函數(shù)聲明的區(qū)別。

我們來(lái)看看為什么梦染,可參考(1)關(guān)于Javascript的函數(shù)聲明和函數(shù)表達(dá)式 (2)關(guān)于JavaScript的變量提升

在Javascript中赡麦,定義函數(shù)有兩種類(lèi)型

函數(shù)聲明

// 函數(shù)聲明
function wscat(type) {
    return type === "wscat";
}

函數(shù)表達(dá)式

// 函數(shù)表達(dá)式
var oaoafly = function(type) {
    return type === "oaoafly";
}

先看下面這個(gè)經(jīng)典問(wèn)題朴皆,在一個(gè)程序里面同時(shí)用函數(shù)聲明和函數(shù)表達(dá)式定義一個(gè)名為getName的函數(shù)

getName() //oaoafly
var getName = function() {
console.log('wscat')
}
getName() //wscat
function getName() {
console.log('oaoafly')
}
getName() //wscat

上面的代碼看起來(lái)很類(lèi)似,感覺(jué)也沒(méi)什么太大差別泛粹。但實(shí)際上遂铡,Javascript函數(shù)上的一個(gè)“陷阱”就體現(xiàn)在Javascript兩種類(lèi)型的函數(shù)定義上。

JavaScript 解釋器中存在一種變量聲明被提升的機(jī)制晶姊,也就是說(shuō)函數(shù)聲明會(huì)被提升到作用域的最前面扒接,即使寫(xiě)代碼的時(shí)候是寫(xiě)在最后面,也還是會(huì)被提升至最前面们衙。

而用函數(shù)表達(dá)式創(chuàng)建的函數(shù)是在運(yùn)行時(shí)進(jìn)行賦值钾怔,且要等到表達(dá)式賦值完成后才能調(diào)用

var getName //變量被提升,此時(shí)為undefined

getName() //oaoafly 函數(shù)被提升 這里受函數(shù)聲明的影響蒙挑,雖然函數(shù)聲明在最后可以被提升到最前面了
var getName = function() {
console.log('wscat')
} //函數(shù)表達(dá)式此時(shí)才開(kāi)始覆蓋函數(shù)聲明的定義
getName() //wscat
function getName() {
console.log('oaoafly')
}
getName() //wscat 這里就執(zhí)行了函數(shù)表達(dá)式的值

所以可以分解為這兩個(gè)簡(jiǎn)單的問(wèn)題來(lái)看清楚區(qū)別的本質(zhì)

var getName;
console.log(getName) //undefined
getName() //Uncaught TypeError: getName is not a function
var getName = function() {
console.log('wscat')
}
var getName;
console.log(getName) //function getName() {console.log('oaoafly')}
getName() //oaoafly
function getName() {
console.log('oaoafly')
}

這個(gè)區(qū)別看似微不足道蒂教,但在某些情況下確實(shí)是一個(gè)難以察覺(jué)并且“致命“的陷阱。出現(xiàn)這個(gè)陷阱的本質(zhì)原因體現(xiàn)在這兩種類(lèi)型在函數(shù)提升和運(yùn)行時(shí)機(jī)(解析時(shí)/運(yùn)行時(shí))上的差異脆荷。

當(dāng)然我們給一個(gè)總結(jié):Javascript中函數(shù)聲明和函數(shù)表達(dá)式是存在區(qū)別的,函數(shù)聲明在JS解析時(shí)進(jìn)行函數(shù)提升懊悯,因此在同一個(gè)作用域內(nèi)蜓谋,不管函數(shù)聲明在哪里定義,該函數(shù)都可以進(jìn)行調(diào)用炭分。而函數(shù)表達(dá)式的值是在JS運(yùn)行時(shí)確定桃焕,并且在表達(dá)式賦值完成后,該函數(shù)才能調(diào)用捧毛。

所以第二問(wèn)的答案就是4观堂,5的函數(shù)聲明被4的函數(shù)表達(dá)式覆蓋了

第三問(wèn)

Foo().getName(); 先執(zhí)行了Foo函數(shù),然后調(diào)用Foo函數(shù)的返回值對(duì)象的getName屬性函數(shù)呀忧。

Foo函數(shù)的第一句getName = function () { alert (1); };是一句函數(shù)賦值語(yǔ)句师痕,注意它沒(méi)有var聲明,所以先向當(dāng)前Foo函數(shù)作用域內(nèi)尋找getName變量而账,沒(méi)有胰坟。再向當(dāng)前函數(shù)作用域上層,即外層作用域內(nèi)尋找是否含有g(shù)etName變量泞辐,找到了笔横,也就是第二問(wèn)中的alert(4)函數(shù),將此變量的值賦值為function(){alert(1)}咐吼。

此處實(shí)際上是將外層作用域內(nèi)的getName函數(shù)修改了吹缔。

注意:此處若依然沒(méi)有找到會(huì)一直向上查找到window對(duì)象,若window對(duì)象中也沒(méi)有g(shù)etName屬性锯茄,就在window對(duì)象中創(chuàng)建一個(gè)getName變量厢塘。
之后Foo函數(shù)的返回值是this,而JS的this問(wèn)題已經(jīng)有非常多的文章介紹,這里不再多說(shuō)俗冻。

簡(jiǎn)單的講礁叔,this的指向是由所在函數(shù)的調(diào)用方式?jīng)Q定的。而此處的直接調(diào)用方式迄薄,this指向window對(duì)象琅关。

遂Foo函數(shù)返回的是window對(duì)象,相當(dāng)于執(zhí)行window.getName()讥蔽,而window中的getName已經(jīng)被修改為alert(1)涣易,所以最終會(huì)輸出1
此處考察了兩個(gè)知識(shí)點(diǎn),一個(gè)是變量作用域問(wèn)題冶伞,一個(gè)是this指向問(wèn)題
我們可以利用下面代碼來(lái)回顧下這兩個(gè)知識(shí)點(diǎn)

var name = "Wscats"; //全局變量
window.name = "Wscats"; //全局變量
function getName() {
    name = "Oaoafly"; //去掉var變成了全局變量
    var privateName = "Stacsw";
    return function() {
        console.log(this); //window
        return privateName
    }
}
var getPrivate = getName("Hello"); //當(dāng)然傳參是局部變量新症,但函數(shù)里面我沒(méi)有接受這個(gè)參數(shù)
console.log(name) //Oaoafly
console.log(getPrivate()) //Stacsw

var name = "Wscats"; //全局變量
window.name = "Wscats"; //全局變量
function getName() {
name = "Oaoafly"; //去掉var變成了全局變量
var privateName = "Stacsw";
return function() {
console.log(this); //window
return privateName
}
}
var getPrivate = getName("Hello"); //當(dāng)然傳參是局部變量,但函數(shù)里面我沒(méi)有接受這個(gè)參數(shù)
console.log(name) //Oaoafly
console.log(getPrivate()) //Stacsw
···
window.Foo().getName();
//->window.getName();
···

第四問(wèn)

直接調(diào)用getName函數(shù)响禽,相當(dāng)于window.getName()徒爹,因?yàn)檫@個(gè)變量已經(jīng)被Foo函數(shù)執(zhí)行時(shí)修改了,遂結(jié)果與第三問(wèn)相同芋类,為1隆嗅,也就是說(shuō)Foo執(zhí)行后把全局的getName函數(shù)給重寫(xiě)了一次,所以結(jié)果就是Foo()執(zhí)行重寫(xiě)的那個(gè)getName函數(shù)

第五問(wèn)

第五問(wèn)new Foo.getName();此處考察的是JS的運(yùn)算符優(yōu)先級(jí)問(wèn)題侯繁,我覺(jué)得這是這題靈魂的所在胖喳,也是難度比較大的一題
下面是JS運(yùn)算符的優(yōu)先級(jí)表格,從高到低排列贮竟±龊福可參考MDN運(yùn)算符優(yōu)先級(jí)、

image.png

image.png

image.png

image.png

這題首先看優(yōu)先級(jí)的第18和第17都出現(xiàn)關(guān)于new的優(yōu)先級(jí)咕别,new (帶參數(shù)列表)比new (無(wú)參數(shù)列表)高比函數(shù)調(diào)用高技健,跟成員訪問(wèn)同級(jí)

new Foo.getName();的優(yōu)先級(jí)是這樣的

相當(dāng)于是:

new (Foo.getName)();
點(diǎn)的優(yōu)先級(jí)(18)比new無(wú)參數(shù)列表(17)優(yōu)先級(jí)高
當(dāng)點(diǎn)運(yùn)算完后又因?yàn)橛袀€(gè)括號(hào)(),此時(shí)就是變成new有參數(shù)列表(18)惰拱,所以直接執(zhí)行new凫乖,當(dāng)然也可能有朋友會(huì)有疑問(wèn)為什么遇到()不函數(shù)調(diào)用再new呢,那是因?yàn)楹瘮?shù)調(diào)用(17)比new有參數(shù)列表(18)優(yōu)先級(jí)低
.成員訪問(wèn)(18)->new有參數(shù)列表(18)
所以這里實(shí)際上將getName函數(shù)作為了構(gòu)造函數(shù)來(lái)執(zhí)行弓颈,遂彈出2帽芽。

答案

function Foo() {
    getName = function () { alert (1); };
    return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

//答案:
Foo.getName();//2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName();//2
new Foo().getName();//3
new new Foo().getName();//3

后續(xù)

后續(xù)我把這題的難度再稍微加大一點(diǎn)點(diǎn)(附上答案),在Foo函數(shù)里面加多一個(gè)公有方法getName翔冀,對(duì)于下面這題如果用在面試題上那通過(guò)率可能就更低了导街,因?yàn)殡y度又大了一點(diǎn),又多了兩個(gè)坑纤子,但是明白了這題的原理就等同于明白了上面所有的知識(shí)點(diǎn)了

function Foo() {
this.getName = function() {
console.log(3);
return {
getName: getName //這個(gè)就是第六問(wèn)中涉及的構(gòu)造函數(shù)的返回值問(wèn)題
}
}; //這個(gè)就是第六問(wèn)中涉及到的搬瑰,JS構(gòu)造函數(shù)公有方法和原型鏈方法的優(yōu)先級(jí)
getName = function() {
console.log(1);
};
return this
}
Foo.getName = function() {
console.log(2);
};
Foo.prototype.getName = function() {
console.log(6);
};
var getName = function() {
console.log(4);
};

function getName() {
console.log(5);
} //答案:
Foo.getName(); //2
getName(); //4
console.log(Foo())
Foo().getName(); //1
getName(); //1
new Foo.getName(); //2
new Foo().getName(); //3
//多了一問(wèn)
new Foo().getName().getName(); //3 1
new new Foo().getName(); //3

最后款票,其實(shí)我是不建議把這些題作為考察面試者的唯一評(píng)判,但是作為一名合格的前端工程師我們不應(yīng)該因?yàn)楦≡旰雎粤宋覀兊囊恍┳罨镜幕A(chǔ)知識(shí)泽论,當(dāng)然我也祝愿所有面試者找到一份理想的工作艾少,祝愿所有面試官找到心中那匹千里馬~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市翼悴,隨后出現(xiàn)的幾起案子缚够,更是在濱河造成了極大的恐慌,老刑警劉巖鹦赎,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谍椅,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡古话,警方通過(guò)查閱死者的電腦和手機(jī)雏吭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)陪踩,“玉大人杖们,你說(shuō)我怎么就攤上這事〖缈瘢” “怎么了胀莹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)婚温。 經(jīng)常有香客問(wèn)我,道長(zhǎng)媳否,這世上最難降的妖魔是什么栅螟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮篱竭,結(jié)果婚禮上力图,老公的妹妹穿的比我還像新娘。我一直安慰自己掺逼,他們只是感情好吃媒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著吕喘,像睡著了一般赘那。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上氯质,一...
    開(kāi)封第一講書(shū)人閱讀 51,763評(píng)論 1 307
  • 那天募舟,我揣著相機(jī)與錄音,去河邊找鬼闻察。 笑死拱礁,一個(gè)胖子當(dāng)著我的面吹牛琢锋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播呢灶,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吴超,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了鸯乃?” 一聲冷哼從身側(cè)響起鲸阻,我...
    開(kāi)封第一講書(shū)人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎飒责,沒(méi)想到半個(gè)月后赘娄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宏蛉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年遣臼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拾并。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡揍堰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嗅义,到底是詐尸還是另有隱情屏歹,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布之碗,位于F島的核電站蝙眶,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏褪那。R本人自食惡果不足惜幽纷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望博敬。 院中可真熱鬧友浸,春花似錦、人聲如沸偏窝。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)祭往。三九已至伦意,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間硼补,已是汗流浹背默赂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留括勺,地道東北人缆八。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓曲掰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親奈辰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子栏妖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容