[面試] 前端工程師一定要會的 JS 觀念題-中英對照之上篇

Hannah Lin
Hannah Lin
Published in
16 min readFeb 8, 2021

1. 前端工程師一定要會的 JS 觀念題-中英對照之上篇
2. 前端工程師一定要會的 JS 觀念題-中英對照之下篇

🔖 文章索引1. 如何讀這篇
2.
What's Scope 作用域?
3.
What's Closure 閉包?
4. What's Hoisting 提升?
5. What's prototype 原型?
6. What's this?
7. What's Event loop?

為什麼會想寫這篇

雖然前端框架蓬勃發展,但面試時還是一定會考純 javaScript 觀念題,畢竟你若連 javaSvript 都不熟,那就別提 React, Vue… 了

網路上關於前端工程師的 JS 考題資源相當多,但除了想分享自己切身面試經歷 30 + 以上矽谷公司一考再考的純 javaScript 核心觀念題 (在我觀察下台灣面試基本上也都是考這些); 還有以下兩個原因

知道如何 “口語解釋” 這些 JS 專有名詞

觀念題在台灣通常都是面試官口頭會問你,在美國則是 Recruiter 電話中會考的題目,主要是測試你對這語言的核心觀念,超過兩題答不出來就掰了也不會有下一關面試機會。所以如何用口語解釋就變得很重要了! 很常遇到知道怎麼寫,但解釋不出來而喪失掉機會那真的很可惜。

用英文面試的需求

疫情下導致遠端工作機會比以往多很多,所以筆者也想鼓勵讀者能脫離舒適圈把眼界放遠國際,但前提是 “必須用英文面試”

英文面試其實沒這麼困難,不用太糾結於文法、也不需要深澀的單字。就算是中文面試你也不會真的背維基百科上的定義給他聽。對方真的聽不懂,你只要一直 for example 舉例,用最口語的範例解釋給對方聽就好。這一篇也會提供中英文答案給讀者。

如何讀這篇

這一篇的目的是提供一個面試目錄的概念,並不會詳細解釋每一題,但會提供當初我在面試前準備的中英文答案,搭配一張圖、簡單程式碼加強記憶。

每一題都會提供推薦閱讀,若對此題概念不熟可以點進去看。建議先看英文的連結,不行的話再看中文。

英文推薦閱讀大部分取自 javaScript.info,它包含大量圖片跟範例,文章會定期更新,這也是當初面試 faceBook 他們提供給我的 reference 之一,自己非常推薦.他也有簡體中文版本但我沒仔細看過翻得好不好

javaScript.info

javaScript 觀念題通常分兩種

  • What is __ ? __ 是什麼
  • What’s the difference between A and B? A 跟 B 有什麼不同

這一篇會著重在第一部分 What is __ ? 因為全部列出來覺得有點太長,所以剩下的會放在下篇

What is XX ?

這就是理論題,通常會拿 JS 專有名詞來考。不需要去背維基百科上的解釋,因為面試官是考你懂不懂而不是背的熟不熟!你可以用自己理解消化後的口語解釋,加上一些範例就行了

Scope 作用域

Where and how to look for things. JS have two lexical scopes global and function

變數在程式中可以被存取的範圍,可分為區域變數,全域變數。

所以 function scope 裡的變數脫離他自己的 scope 會讀不到

推薦閱讀

Closure 閉包

Closure is when a function “remembers” its lexical scope even when the function is executed outside that lexical scope. The combination of the function and it’s environment is known as a closure.

閉包是個捕捉了外部函式變數(或使之繼續存活)的函式,它包含了一個函式,以及函式被建立時所在的環境。所以基本上看到一個內部函式 ( function is defined inside another function),就有了 closure

bar 建立了 closure,因為它除了是函式外還包含 x 這個從外部環境來的變數

基本上函式離開執行環境時,也會同時將佔用的記憶體空間給釋放出來。例如以上 x變數應該在執行完畢就會在 memory (記憶體) 中被清掉。但因為 closure 特性此 x變數還會繼續被保留

進階考題: 可以應用在哪 ?

以下是在 JS 中非常常見的寫法,也是運用 closure 特性讓 story 變數可以一直被保留並且做運算

var storyWriter = function(initString = ""){
var story = initString;

function addWords(string){
story += string
return this;
}

function erase(){
story = "";
return this;
}

function print() {
return story;
}

return {
addWords: addWords,
erase: erase,
print: print
}
}

storyWriter().addWords('444').addWords('444').print() // 444444
storyWriter().addWords('444').erase().addWords('hi').print() // hi
/**
* @param {number} n
* @return {Function} counter
*/
var createCounter = function(n) {
var count = n;
return () => count++
};

/**
* const counter = createCounter(10)
* counter() // 10
* counter() // 11
* counter() // 12
*/

推薦閱讀

Hoisting 提升

JavaScript’s default behavior of moving declarations to the top.

紅色是會被 hoisting 的部分
var a = 0;
function a() {};
a();
// Uncaught TypeError: a is not a function
// 實際上
function a() {};
var a;
a = 0
a();

延伸考題: 那 let 跟 const 有 hoisting 嗎?

All declarations are “hoisted”, the difference between var/function declarations and let/const/class declara­tions is the initialisation.

還是有 hoisting ,只是初始化行為跟 var 不同

  • var: 變數會被初始化為 undefined
  • let, const : 不會先被初始化所以在賦值之前的 Temporal Dead Zone(TDZ, 時間死區) 取值會發生錯誤

推薦閱讀

prototype 原型

A prototype is an object that can inherit other object properties.

Every JavaScript object has a prototype. All JavaScript objects inherit their properties and methods from their prototype.

透過「原型」繼承可以讓本來沒有某個屬性的物件去存取其他物件的屬性。

function Person(faceColor, hairStyle, hairColor) {
this.faceColor = faceColor;
this.hairStyle = hairStyle;
this.hairColor = hairColor;
this.makePonytail = function(){
if(this.hairStyle !== 'long'){ return false }
return true;
}
}
var mySister = new Person("skin", "long", "black");
var myMother = new Person("black", "short", "brown");
myMother.nationality = "African American";
Person.prototype.hair = function() {
return this.hairStyle + " " + this.hairColor;
};
mySister.__proto__ === Person.prototype // true
mySister.__proto__.constructor === Person // true

延伸閱讀

this

Depends on how the function is called. the JavaScript this keyword refers to the object it belongs to.

每一個 function 在被執行的時候都會有一個 reference 指向所屬的環境,這就是 this,有四種函數調用方法

  • In normal function calls 一般呼叫涵式: this refers to the global object.
  • Within methods on objects 方法調用: this refers to the owner object.
  • Within an object that has been constructed 建構涵式調用: this refers to the object that has been constructed
  • A function invoked with .call, .apply, or bind 隱式調用: can refer this to any object.

以下這個 this 代表的是什麼呢 ?

function a() {
console.log(this)
}

答案是不知道,因為 this 取決于執行這個 function 時的環境

/* In normal function calls */
a(); // this 就是 window
/* Within methods on objects */
var obj = {};
obj.a = a;
obj.a(); // 這時 this 就是 obj
/* Within an object that has been constructed */
var b = new a();
// 這時 this 會是 a 這個 function 所產生的 object
/* A function invoked with .call, .apply, or bind */
a.call(obj);
// 這時 this 也是 obj

BUT, JavaScript loses scope of this when used inside of a function that is contained another function.

var cat = {
name: 'Gus',
printInfo: function(){
console.log(this); // cat 這個 object

function nestedFunction(){
console.log(this); // window
}
nestedFunction()
}
}

延伸考題: arrow function 裡的 this 跟一般的函式有什麼差別?

  1. 一般的函式 this 取決于執行時是誰呼叫他、arrow function 當中的 this 是定義時身處在的環境 (function scope) 當中 this 指向誰而不是使用時的對象 (會持續往上找,沒找到就一直到最上層的 window)
  2. arrow function 中沒有 this,也無法直接使用 bind, apply, call 修改 this 的指向
  3. arrow function 不能作為 constructor 使用
const test = {
prop: 42,
arrowFunc: () => {
console.log('arrowFunc:', this) // window (上一層是 global)

// 裡面不管是 normal function 或 arrow function 再幾層 this 都是 window
},
normalFunc: function () {
console.log('normalFunc: ', this) // test 這個 obj (取決於呼叫時)
const a = () => {
console.log(`a ${JSON.stringify(this)}!`);
// test 這個 obj (這個 function scope 指向 obj)

const b = () => {
console.log(`b: ${JSON.stringify(this)}!`);
// test 這個 obj (這個 function scope 指向 obj)
}
b();

function c(){
console.log('c', this)
// window,
// 因為 normal function 中 function 包 function this 會變 window
}
c()
}
a()

},
};

test.func();

推薦閱讀

Event loop

(我很口語的跟面試官解釋如下) JS is one thread. so it only do one thing at a time. But if something stuck there like calling API need 3 seconds, program do nothing but wait? Not exactly! Event loop come to solve this problem. Every time has asynchronous operation, just need to put them in event queue, wait other things be finished then back to run the code.

JS 是單執行緒,所有同步性的工作,瀏覽器會一個個執行,但遇到非同步的操作就會先放到一個叫做 task queue 的地方,等到瀏覽器目前沒有其他工作,就會到 task queue 看看有沒有還沒執行的任務,再把它拿出來執行。

https://www.educative.io/edpresso/what-is-an-event-loop-in-javascript

優先執行順序

  1. Tasks
  2. microtasks 如 promise
  3. queues 非同步事件如 click、setTimeout、ajax

那以下會輸出什麼呢?

var a = 1;
function fn() {
setTimeout(function() {
a = 2;
}, 0);
}
function fn2() {
setTimeout(function() {
a = 3;
}, 0);
}
fn();
fn2();
console.log(a); // 1
// ---- setTimout 會等全部 call stack 都做完才開始
可以用這個連結理解 event loop

經典考題

請問下列會輸出什麼?

for(var i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i)
}, 0)
}

答案是會印出 5 次 6 !這對於 JS 老手來說應該超容易,但你也要懂的如何解釋, JS 是如何跑才會導致這種結果。若要印出 1 2 3 4 5 可以這樣寫

// 方法 1
for(let i = 1; i <= 5; i++) {
setTimeout(function() { console.log(i) }, 0)
}
// 方法 2 IIFE
for(var i = 1; i <= 5; i++) {
(function (x) {
setTimeout(function() { console.log(x) }, 0)
})(i)
}

延伸閱讀

所以說event loop到底是什麼玩意兒?| Philip Roberts | JSConf EU (這影片太經典一定要推薦)

另外若對 queue 跟 stack 不知是啥也歡迎參考我之前寫過淺顯易懂的解釋喔

--

--