14 個折磨人的 JavaScript 面試題

leftstick發表於2016-08-07

前端工程師有時候面試時會遇到一類面試官,他們問的問題對於語言本身非常較真兒,往往不是候選人可能期待的面向實際的問題(有些候選人強調能幹活就行,至於知不知道其中緣由是無關痛癢的)。這類題目,雖然沒有邏輯,但某種程度說,確實考察了候選人對於javascript這門語言的理解。

突然想到這個話題是無聊在翻自己的Github,看看以前都寫過什麼醜貨。然後翻到了這篇解釋Javascript quiz的文章quiz-legend,反正沒事兒,就想搬過來供大家學習、理解、背誦、批判。

問題一

(function(){
    return typeof arguments;//"object"
})();

arguments是一個Array-like物件,對應的就是傳入函式的引數列表。你可以在任何函式中直接使用該變數。

typeof操作符只會返回string型別的結果。參照如下列表可知對應不同資料,typeof返回的值都是什麼:

型別 結果
undefined 'undefined'
null 'object'
Boolean 'boolean'
Number 'number'
String 'string'
Symbol (new in ECMAScript 2015) 'symbol'
Host object (provided by the JS environment) Implementation-dependent
Function object (implements [[Call]] in ECMA-262 terms) 'function'
Any other object 'object'

由此我們推斷出,typeof argumentsobject

問題二

var f = function g(){ return 23; };
typeof g();//報錯

這是一個名字是g的function expression,然後又被賦值給了變數f

這裡的函式名g和被其賦值的變數f有如下差異:

  • 函式名g不能變動,而變數f可以被重新賦值
  • 函式名g只能在函式體內部被使用,試圖在函式外部使用g會報錯的

問題三

(function(x){
    delete x;
    return x;//1
})(1);

delete操作符可以從物件中刪除屬性,正確用法如下:

delete object.property
delete object['property']

delete操作符只能作用在物件的屬性上,對變數和函式名無效。也就是說delete x是沒有意義的。

你最好也知道,delete是不會直接釋放記憶體的,她只是間接的中斷物件引用

問題四

var y = 1, x = y = typeof x; x;//"undefined"

我們試圖分解上述程式碼成下面兩步:

var y = 1; //step 1
var x = y = typeof x; //step 2

第一步應該沒有異議,我們直接看第二步

  1. 賦值表示式從右向左執行
  2. y被重新賦值為typeof x的結果,也就是undefined
  3. x被賦值為右邊表示式(y = typeof x)的結果,也就是undefined

問題五

(function f(f){
    return typeof f();//"number"
})(function(){ return 1; });

直接上註釋解釋:

(function f(f){
    //這裡的f是傳入的引數function(){ return 1; }
    //執行的結果自然是1
    return typeof f(); //所以根據問題一的表格我們知道,typeof 1結果是"number"
})(function(){ return 1; });

問題六

var foo = {
    bar: function() { return this.baz; },
    baz: 1
};

(function(){
    return typeof arguments[0]();//"undefined"
})(foo.bar);

這裡你可能會誤以為最終結果是number。向函式中傳遞引數可以看作是一種賦值,所以arguments[0]得到是是真正的bar函式的值,而不是foo.bar這個引用,那麼自然this也就不會指向foo,而是window了。

問題七

var foo = {
    bar: function(){ return this.baz; },
    baz: 1
}
typeof (f = foo.bar)();//"undefined"

這和上一題是一樣的問題,(f = foo.bar)返回的就是bar的值,而不是其引用,那麼this也就指的不是foo了。

問題八

var f = (function f(){ return '1'; }, function g(){ return 2; })();
typeof f;//"number"

逗號操作符 對它的每個操作物件求值(從左至右),然後返回最後一個操作物件的值

所以(function f(){ return '1'; }, function g(){ return 2; })的返回值就是函式g,然後執行她,那麼結果是2;最後再typeof 2,根據問題一的表格,結果自然是number

問題九

var x = 1;
if (function f(){}) {
    x += typeof f;
}
x;//"1undefined"

這個問題的關鍵點,我們在問題二中談到過,function expression中的函式名f是不能在函式體外部訪問的

問題十

var x = [typeof x, typeof y][1];
typeof typeof x;//"string"
  1. 因為沒有宣告過變數y,所以typeof y返回"undefined"
  2. typeof y的結果賦值給x,也就是說x現在是"undefined"
  3. 然後typeof x當然是"string"
  4. 最後typeof "string"的結果自然還是"string"

問題十一

(function(foo){
    return typeof foo.bar;//"undefined"
})({ foo: { bar: 1 } });

這是個純粹的視覺詭計,上註釋

(function(foo){

    //這裡的foo,是{ foo: { bar: 1 } },並沒有bar屬性哦。
    //bar屬性是在foo.foo下面
    //所以這裡結果是"undefined"
    return typeof foo.bar;
})({ foo: { bar: 1 } });

問題十二

(function f(){
    function f(){ return 1; }
    return f();//2
    function f(){ return 2; }
})();

通過function declaration宣告的函式甚至可以在宣告之前使用,這種特性我們稱之為hoisting。於是上述程式碼其實是這樣被執行環境解釋的:

(function f(){
    function f(){ return 1; }
    function f(){ return 2; }
    return f();
})();

問題十三

function f(){ return f; }
new f() instanceof f;//false

當程式碼new f()執行時,下面事情將會發生:

  1. 一個新物件被建立。它繼承自f.prototype
  2. 建構函式f被執行。執行的時候,相應的傳參會被傳入,同時上下文(this)會被指定為這個新例項。new f等同於new f(),只能用在不傳遞任何引數的情況。
  3. 如果建構函式返回了一個“物件”,那麼這個物件會取代整個new出來的結果。如果建構函式沒有返回物件,那麼new出來的結果為步驟1建立的物件,

ps:一般情況下建構函式不返回任何值,不過使用者如果想覆蓋這個返回值,可以自己選擇返回一個普通物件來覆蓋。當然,返回陣列也會覆蓋,因為陣列也是物件。

於是,我們這裡的new f()返回的仍然是函式f本身,而並非他的例項

問題十四

with (function(x, undefined){}) length;//2

with語句將某個物件新增的作用域鏈的頂部,如果在statement中有某個未使用名稱空間的變數,跟作用域鏈中的某個屬性同名,則這個變數將指向這個屬性值。如果沒有同名的屬性,則將丟擲ReferenceError異常。

OK,現在我們來看,由於function(x, undefined){}是一個匿名函式表示式,是函式,就會有length屬性,指的就是函式的引數個數。所以最終結果就是2

寫在最後

有人覺得這些題坑爹,也有人覺得開闊了眼界,見仁見智吧。但有一件事是真的,無論你是否堅定的實踐派,缺了理論基礎,也鐵定走不遠 - 你永遠不會見到哪個熟練的技術工人突然成了火箭專家。

看文件、讀標準、結合實踐,才是同志們的決勝之道。

相關文章