iT邦幫忙

7

17道 JS 面試題目的解析

前言

本篇文章的題目都來自於以下網站:
https://www.toptal.com/javascript/interview-questions
這幾天我看了網站內的題目,然後選了網站內的一些題目翻成中文,並參考網站內原本的解析再加上自己的一些理解,就誕生了這篇文章哈哈~
/images/emoticon/emoticon07.gif
可以自己先試著回答問題,再往下看答案,假如有看到我寫錯的地方,非常歡迎告訴我,感謝你~

題目1

(function(){
  var a = b = 3;
})();

console.log("a defined? " + (typeof a !== 'undefined'));
console.log("b defined? " + (typeof b !== 'undefined'));

問以上程式碼執行結果為何?

答案:
a defined? false / b defined? true

原因:
var a = b = 3; 代表:

b = 3;
var a = b;

在非嚴格模式下,假如變數未做宣告就賦值,則都為全域變數,以下都是全域變數

mom = '老媽';

(function () {
  mom = '老媽';
})();

因此 b = 3,defined == true
而 a 為區域變數,在 console 時已經出去作用域範圍,因此為 undefined

題目2

var myObject = {
    foo: "bar",
    func: function() {
        var self = this;
        console.log("outer func:  this.foo = " + this.foo);
        console.log("outer func:  self.foo = " + self.foo);
        (function() {
            console.log("inner func:  this.foo = " + this.foo);
            console.log("inner func:  self.foo = " + self.foo);
        }());
    }
};
myObject.func();

問以上程式碼執行結果為何?

答案:

outer func: this.foo = bar
outer func: self.foo = bar
inner func: this.foo = undefined
inner func: self.foo = bar

原因:
在 outer 函式,this 和 self 都指向 myObject,因此輸出都是 bar,但在 inner 函式,由於 IIFE 有自己的作用域,因此 this 輸出為 undefined,但是 self 因為保存了 this 的指向,因此輸出為 bar

題目3

function foo1()
{
  return {
      bar: "hello"
  };
}

function foo2()
{
  return
  {
      bar: "hello"
  };
}

console.log(foo1());
console.log(foo2());

問以上程式碼執行結果為何?

答案:
foo1() 回傳 Object {bar: "hello"}
foo2() 回傳 undefined

原因:
return 如果後面沒有任何東西(緊接著換行),會自動補上;,因此foo2() 才會回傳 undefined

補充: breakcontinue也一樣會自動補上;

題目4

寫一個方法 isInterger(x),用來判斷一個變數是否是整數

答案:
用這個寫法會在 x 為很大值出錯

function isInteger(x) { return parseInt(x, 10) === x; }

正解:

function isInteger(x) { return Math.round(x) === x; }

Math.ceil()Math.floor()也可以

題目5

寫一個按照下面方式調用都能正常工作的 sum 方法

console.log(sum(2,3)); // Outputs 5
console.log(sum(2)(3)); // Outputs 5

答案:
方法1: 使用 arguments

function sum(x) {
  if(arguments.length == 2) {
      return arguments[0] + arguments[1];
  } else {
      return (y) => x + y;
  }
}

方法2: 假如呼叫函式時帶入的參數小於函式內設定的參數,則那些參數會設定 undefined

function sum(x, y) {
  if (y !== undefined) {
    return x + y;
  } else {
    return (y) => x + y;
  }
}

題目6

for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function(){ console.log(i); });
  document.body.appendChild(btn);
}

// 執行上面JS後出現5個按鈕:
<button>Button 0</button>
<button>Button 1</button>
<button>Button 2</button>
<button>Button 3</button>
<button>Button 4</button>

問題1: 點擊按鈕4,會印出什麼?
問題2: 要怎麼樣修正才會讓點擊按鈕後出現按鈕的數字?

問題1答案:
5,點擊其它按鈕也都是5
事件監聽概念和 setTimeout 差不多,非同步行為要等到同步行為完成後才開始運作,因此當 for 迴圈執行完 i 值是5,不管怎麼點都會印出5

問題2答案:
方法1: 使用 IIFE 將每次迴圈的 i 值保存,並把 i 值傳給 j ,就能正常運行了

for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', (function(j) {
    return function() { console.log(j); };
  })(i));
  document.body.appendChild(btn);
}

方法2: 或是將整個監聽事件放入 IIFE

for (var i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  (function (j) {
    btn.addEventListener('click', function() { console.log(j); });
  })(i);
  document.body.appendChild(btn);
}

方法3: 使用 forEach,使用陣列索引當 i

['a', 'b', 'c', 'd', 'e'].forEach(function (value, i) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function() { console.log(i); });
  document.body.appendChild(btn);
});

方法4: 把 var 改成 let,利用的是 let 區塊作用域({})的特性,var 在 for 運作完後還存在全域,但 let 在全域就消失,只有進入 for 才會重新產生 i 並給值

for (let i = 0; i < 5; i++) {
  var btn = document.createElement('button');
  btn.appendChild(document.createTextNode('Button ' + i));
  btn.addEventListener('click', function(){ console.log(i); });
  document.body.appendChild(btn);
}

附圖:

題目7

var arr1 = "john".split('');
var arr2 = arr1.reverse();
var arr3 = "jones".split('');
arr2.push(arr3);
console.log("array 1: length=" + arr1.length + " last=" + arr1.slice(-1));
console.log("array 2: length=" + arr2.length + " last=" + arr2.slice(-1));

問以上程式碼執行結果為何?

答案:

array 1: length=5 last=j,o,n,e,s
array 2: length=5 last=j,o,n,e,s

原因:

  1. 使用 reverse(),會改變原本的陣列,並回傳陣列 =>arr1, arr2 兩個都變成["n", "h", "o", "j"]
  2. 因為 reverse() 回傳的是原本陣列的 reference,因此之後產生的陣列(arr2)和原本陣列(arr1)都指向同一個記憶體位置
  3. 在修改 arr2 的時候,同時也修改到 arr1 ,因為實際上是修改它們記憶體內的東西。=>arr1, arr2 兩個都變成["n", "h", "o", "j", ["j", "o", "n", "e", "s"]] (二維陣列)
  4. slice(-1) 代表的是拷貝陣列最後一個元素,並進行回傳,因此取到的是["j", "o", "n", "e", "s"],因此arr1.slice(-1)arr2.slice(-1)都是["j", "o", "n", "e", "s"]

題目8

console.log(1 +  "2" + "2");
console.log(1 +  +"2" + "2");
console.log(1 +  -"1" + "2");
console.log(+"1" +  "1" + "2");
console.log( "A" - "B" + "2");
console.log( "A" - "B" + 2);

問以上程式碼執行結果為何?

答案:

122
32
02
112
NaN2
NaN

原因:
console.log(1 + "2" + "2");: 最開頭的1會被轉成字串連接
console.log(1 + +"2" + "2");: +"2" 會轉為 數字正2,和1相加後變成3,再轉成字串和"2"連接
console.log(1 + -"1" + "2");: -"2" 會轉為 數字負2,和1相加後變成0,再轉成字串和"2"連接
console.log(+"1" + "1" + "2");: +"1" 會轉為 數字正1,但後面是字串所以又被轉成字串連接
console.log( "A" - "B" + "2");: 因為"A" - "B"根本不能減,因此輸出 NaN,代表非數字,然後再和"2"連接
console.log( "A" - "B" + 2);: NaN 和 數字相加結果還是 NaN

題目9

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        nextListItem();
    }
};

假如 list 陣列太大,將會導致 stack overflow,如何在不改變遞迴的前提下修改這段程式?

答案:
使用 setTimeout,因為 setTimeout 這種非同步行為會放到 task queue(或叫event queue),然後搭配 event loop 一次只放一個遞迴的呼叫到 call stack,就解決在 call stack 會 stack overflow 的問題。

var list = readHugeList();

var nextListItem = function() {
    var item = list.pop();

    if (item) {
        setTimeout( nextListItem, 0);
    }
};

題目10

var a={},
    b={key:'b'},
    c={key:'c'};

a[b]=123;
a[c]=456;

console.log(a[b]);

問以上程式碼執行結果為何?

答案:
456

原因:
在 JS 中,物件內的 key 會被隱性轉為字串,b 物件加入 a 物件時變成"[object Object]",因此在a[b]=123;這行,a 變成:

{
  "[object Object]":123
}

而在這行a[c]=456;,就把 a 物件變成:

{
  "[object Object]":456
}

故輸出為456

題目11

var hero = {
    _name: 'John Doe',
    getSecretIdentity: function (){
        return this._name;
    }
};

var stoleSecretIdentity = hero.getSecretIdentity;

console.log(stoleSecretIdentity());
console.log(hero.getSecretIdentity());

問以上程式碼執行結果為何?假如有出錯,該如何修復?

答案:

undefined
John Doe

原因:
因為呼叫stoleSecretIdentity()的時候,getSecretIdentity()函式從 hero 物件拿出,為全域的狀態,因此 this 指向全域,找不到_name導致印出 undefined

如何修復:
使用 var stoleSecretIdentity = hero.getSecretIdentity.bind(hero); 修復,bind() 會建立一個新函式,而這個函式的 this 會指向 bind() 裡的第一個參數

題目12

var length = 10;
function fn() {
	console.log(this.length);
}

var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};

obj.method(fn, 1);

問以上程式碼執行結果為何?

答案:

10
2

原因:
在 method() 呼叫時,該方法的fun()印出10,因為 this 指向全域,而arguments[0]();,我們可以把 arguments 這個類陣列看成arguments = [fn(), 1];,這時fn()的 this 指向這個陣列,因此最後輸出為2,因為arguments.length為2

題目13

(function () {
    try {
        throw new Error();
    } catch (x) {
        var x = 1, y = 2;
        console.log(x);
    }
    console.log(x);
    console.log(y);
})();

問以上程式碼執行結果為何?

答案:

1
undefined
2

原因:
這段程式碼可以看成:

(function () {
    var x, y;
    try {
        throw new Error();
    } catch (x) {
        x = 1;
        y = 2;
        console.log(x);
    }
    console.log(x);
    console.log(y);
})();

由於 hoisting,x 和 y 出現在函式最上面,然後catch (x)的 x 是 Error 物件,而後被賦值為1,因此 catch 裡的 console 印出1

題目14

var x = 21;
var girl = function () {
    console.log(x);
    var x = 20;
};
girl ();

問以上程式碼執行結果為何?

答案: undefined

原因:
根據 hoisting 的特性,這段程式碼可看成:

var x = 21;
var girl = function () {
    var x;
    console.log(x);
    x = 20;
};
girl ();

一看就了解了,不多解釋

題目15

要如何複製一個物件?

答案:

var obj = {a: 1 ,b: 2}
var objclone = Object.assign({},obj);

不過使用Object.assign無法做到完全的深拷貝,在複製後的新物件裡面的物件還是和原本物件裡的物件指向相同記憶體位置。

let obj = {
    a: 1,
    b: 2,
    c: {
        age: 30
    }
};

var objclone = Object.assign({},obj);
console.log('objclone: ', objclone);

obj.c.age = 45;
console.log('After Change - obj: ', obj);           // 45
console.log('After Change - objclone: ', objclone); // 45

題目16

console.log(1 < 2 < 3);
console.log(3 > 2 > 1);

問以上程式碼執行結果為何?

答案:

true
false

原因:
<>兩個運算符為由左到右計算
1 < 2 為 true,然後 true < 3,true == 1,因此結果是 true
3 > 2 為 true, 然後 true > 1,true == 1 ,因此結果是 false

題目17

console.log(typeof typeof 1);

問以上程式碼執行結果為何?

答案:
string

原因:
typeof 1 返回 "number" 然後 typeof "number" 返回 string


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
阿展展展
iT邦好手 1 級 ‧ 2019-11-12 21:02:00

第一題我就被打敗惹/images/emoticon/emoticon46.gif

harry xie iT邦研究生 1 級 ‧ 2019-11-13 09:08:54 檢舉

/images/emoticon/emoticon06.gif

0
no027843
iT邦新手 5 級 ‧ 2020-04-09 22:17:41

感想分享!!
想請問大大學前端學多久了,最近在自學大概快半年了,目前是將js,html,css基礎都了解一遍,現在在學bootstrap4,Jquery,react.js,常常遇到瓶頸,但查一段時間資料還是能解決,目前沒給自己訂什麼目標,用純js+html+css寫過一個to do list而已,想說能不能跟前輩請益,有沒有什麼好建議能夠提升自己,或是設個什麼目標之類的,對之後找工作會有幫助,如果前輩願意分享自身經歷我也非常樂意!!謝謝!!

harry xie iT邦研究生 1 級 ‧ 2020-04-10 10:02:32 檢舉

你好,目前是自學大約兩年囉~主要是一邊念大學(本身是資訊科系相關的),並利用課餘時間學習前端

然後你說目前沒什麼目標,但有想提升自己,那麼我建議你可以運用你所學的實作一些小作品,可以做一個小時鐘,計算機,串個 api 之類的,這邊推薦 JS30,裡面有很多的小作品可以實作。

最後有看到你自己去查資料去解決問題的,不錯,這行就是需要自己解決問題的能力,加油吧/images/emoticon/emoticon12.gif

no027843 iT邦新手 5 級 ‧ 2020-04-11 10:15:13 檢舉

原來是這樣,我也有打算做一些小專案,我看過前輩給的JS30,超讚的裡面一堆看起來很厲害的專案,而且也能訓練自己的英文能力!
那前輩當時在做JS30這個系列的時候,是跟著課程裡一步一步做嗎?
事後有在複習之類的嗎?不知道怎樣學法去應用這個資源會發揮最大化!
這點是想請教前輩的一點。
另外前輩目前是已經在職了嗎?謝謝您!

我要留言

立即登入留言