面試題是招募公司和開發者都非常關心的話題,公司希望透過它了解開發者的真實水平和細節處理能力,而開發者希望能夠最大程度地展示自己的水平(甚至超常發揮)。本文提供了眾多前端開發面試題,無論是招募方或應徵者都值得一看!
前言
年前剛剛離職了,分享下我曾經出過的一道面試題,此題是我出的一套前端面試題中的最後一題,用來考核面試者的JavaScript的綜合能力,很可惜到目前為止的將近兩年中,幾乎沒有人能夠完全答對,並非多難只是因為大多面試者過於輕視他。
題目如下:
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(); getName(); Foo().getName(); getName(); new Foo.getName(); new Foo().getName(); new new Foo().getName();
答案是:
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
此題是我綜合之前的開發經驗以及遇到的JS各種坑匯集而成。此題涉及的知識點眾多,包括變數定義提升、this指標指向、運算子優先權、原型、繼承、全域變數污染、物件屬性及原型屬性優先權等等。
此題包含7小問,分別說下。
第一問
先看此題的上半部做了什麼,首先定義了一個叫Foo的函數,之後為Foo創建了一個叫getName的靜態屬性存儲了一個匿名函數,之後為Foo的原型對像新創建了一個叫getName的匿名函數。之後又透過函數變數表達式建立了一個getName的函數,最後再宣告一個叫getName函數。
第一問的 Foo.getName 自然是存取Foo函數上儲存的靜態屬性,自然是2,沒什麼好說的。
第二問
第二問,直接呼叫 getName 函數。既然是直接呼叫就是存取目前上文作用域內的叫getName的函數,所以跟1 2 3都沒什麼關係。此題有無數面試者回答為5。此處有兩個坑,一是變數宣告提升,二是函數表達式。
變數宣告提升
即所有宣告變數或宣告函數都會被提升到目前函數的頂端。
例如下碼:
console.log('x' in window);//true var x; x = 0;
程式碼執行時js引擎會將宣告語句提升至程式碼最上方,變成:
var x; console.log('x' in window);//true x = 0;
函數表達式
var getName 與 function getName 都是宣告語句,差別在於 var getName 是函數表達式,而 function getName 是函數宣告。關於JS中的各種函數創建方式可以看 大部分人都會做錯的經典JS閉包面試題 這篇文章有詳細說明。
函數表達式最大的問題,在於js會將此程式碼拆分為兩行程式碼分別執行。
例如下碼:
console.log(x);//输出:function x(){} var x=1; function x(){}
實際執行的程式碼為,先將var x=1 拆分為var x; 和x = 1; 兩行,再將var x; 和function x(){} 兩行提升至最上方變成:
var x; function x(){} console.log(x); x=1;
所以最終函數宣告的x覆蓋了變數宣告的x,log輸出為x函數。
同理,原題程式碼最終執行時的是:
function Foo() { getName = function () { alert (1); }; return this; } var getName;//只提升变量声明 function getName() { alert (5);}//提升函数声明,覆盖var的声明 Foo.getName = function () { alert (2);}; Foo.prototype.getName = function () { alert (3);}; getName = function () { alert (4);};//最终的赋值再次覆盖function getName声明 getName();//最终输出4
3 番目の質問
3 番目の質問 Foo().getName(); は、まず Foo 関数を実行し、次に Foo 関数の戻り値オブジェクトの getName 属性関数を呼び出します。
Foo 関数 getName = function () {alert (1) }; の最初の文は、var 宣言がないことに注意してください。そのため、最初に現在の Foo で getName 変数を探します。関数スコープは存在しません。次に、現在の関数スコープの上位層、つまり外側のスコープを調べて、getName 変数が含まれているかどうかを確認します。これが、2 番目の質問のalert(4) 関数です。 function(){alert(1) } への変数。
ここでは、実際に変更された外側スコープの getName 関数を示します。
注: ここでまだ見つからない場合は、ウィンドウ オブジェクトまで検索します。ウィンドウ オブジェクトに getName 属性がない場合は、ウィンドウ オブジェクトに getName 変数を作成します。
その後、Foo関数の戻り値はこうなります。JSのこの問題についてはブログガーデンにすでにたくさんの記事があるので、ここでは詳しく説明しません。
簡単に言うと、関数の呼び出し方法でポイントが決まります。ここでの直接呼び出しメソッドでは、this は window オブジェクトを指します。
Foo 関数は window オブジェクトを返します。これは window.getName() を実行するのと同じであり、ウィンドウ内の getName はalert(1) に変更されているため、最後に 1 が出力されます
ここでは 2 つの知識ポイントを検討します。1 つは変数スコープの問題、もう 1 つはこのポインティングの問題です。
4 番目の質問
getName 関数を直接呼び出します。これは window.getName() と同等です。この変数は Foo 関数の実行時に変更されているため、結果は 3 番目の質問と同じになります。
5 番目の質問
5 番目の質問は new Foo.getName(); で、ここで調べられるのは js の演算子の優先順位の問題です。
js 演算子の優先順位:
上記の表を調べると、ポイント (.) の優先順位が新しい操作よりも高いことがわかります。これは、以下と同等です。
new (Foo.getName)();
です。
コンストラクターの戻り値
最初に Foo のインスタンス化されたオブジェクトを初期化し、次にそのプロトタイプで getName 関数を新しいコンストラクターとして再度使用します。
最終結果は 3 です
ついに
就答案狀況而言,第一問100%都可以回答正確,第二問大概只有50%正確率,第三問能回答正確的就不多了,第四問再正確就非常非常少了。其實此題並沒有太多刁鑽匪夷所思的用法,都是一些可能會遇到的場景,而大多數人但凡有1年到2年的工作經驗都應該完全正確才對。
只能說有些人太急躁太輕視了,希望大家透過此文了解js一些特性。