ホームページ  >  記事  >  ウェブフロントエンド  >  (JS)_javascript スキルが軽視されることが多い、Web フロントエンドの面接でよくある質問

(JS)_javascript スキルが軽視されることが多い、Web フロントエンドの面接でよくある質問

WBOY
WBOYオリジナル
2016-05-16 15:15:351508ブラウズ

面試題是招募公司和開發者都非常關心的話題,公司希望透過它了解開發者的真實水平和細節處理能力,而開發者希望能夠最大程度地展示自己的水平(甚至超常發揮)。本文提供了眾多前端開發面試題,無論是招募方或應徵者都值得一看!

前言

年前剛剛離職了,分享下我曾經出過的一道面試題,此題是我出的一套前端面試題中的最後一題,用來考核面試者的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)();

つまり、getName 関数は実際にはコンストラクターとして実行され、2 がポップアップ表示されます。


質問 6


6 番目の質問は new Foo().getName() です。まず、演算子の優先順位は new よりも高くなります。実際の実行は

です。

(new Foo()).getName()


次に、Foo 関数が最初に実行され、Foo はコンストラクターとして戻り値を持ちます。そこで、ここで js のコンストラクターの戻り値の問題について説明する必要があります。


コンストラクターの戻り値

従来の言語では、コンストラクターは戻り値を持つべきではありません。実際の実行の戻り値は、このコンストラクターのインスタンス化されたオブジェクトです。


js では、コンストラクターは戻り値を持つことも持たないこともできます。


1. 戻り値がない場合は、他の言語と同様にインスタンス化されたオブジェクトが返されます。


2. 戻り値がある場合、戻り値が参照型かどうかを確認します。基本型 (文字列、数値、ブール型、null、未定義) などの非参照型の場合は、戻り値がないのと同じであり、そのインスタンス化されたオブジェクトが実際に返されます。


3. 戻り値が参照型の場合、実際の戻り値はこの参照型になります。


元の質問では、 this が返され、これはもともとコンストラクター内の現在のインスタンス化されたオブジェクトを表すため、Foo 関数は最終的にインスタンス化されたオブジェクトを返します。


次に、インスタンス化されたオブジェクトの getName 関数を呼び出します。Foo コンストラクターではインスタンス化されたオブジェクトに属性が追加されないため、現在のオブジェクトのプロトタイプ オブジェクトで getName を探して見つけます。


最終的な出力は 3 です。


質問 7


7 番目の質問 new new Foo().getName(); もオペレーターの優先順位の問題です。


実際の最終的な実行は次のとおりです:


new ((new Foo()).getName)();


最初に Foo のインスタンス化されたオブジェクトを初期化し、次にそのプロトタイプで getName 関数を新しいコンストラクターとして再度使用します。


最終結果は 3 です


ついに

就答案狀況而言,第一問100%都可以回答正確,第二問大概只有50%正確率,第三問能回答正確的就不多了,第四問再正確就非常非常少了。其實此題並沒有太多刁鑽匪夷所思的用法,都是一些可能會遇到的場景,而大多數人但凡有1年到2年的工作經驗都應該完全正確才對。

只能說有些人太急躁太輕視了,希望大家透過此文了解js一些特性。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。