一些面試題與相關知識點

今天面試提出的基本知識點,有些屬於不熟,有些是學了就忘,整理與紀錄

  • Hoisting 問題
  • CSS 相關問題
  • JS 相關問題

面試問題

什麼是 hoisting

屬於 var 宣告優先序問題

舉簡單例子

1
2
console.log(a) //undifiend
var a = 5

此例子中可以想像程式這樣執行

1
2
3
var a
console.log(a) //undifiend
a = 5

搭配 function 型態的變種則為

1
2
3
4
5
6
function test(v){
console.log(v)
var v = 3
}
test(5) //5

因為在 function 呼叫時會初始化變數,可以想像程式是這樣執行

1
2
3
4
5
6
7
8
function test(v){
var v = 5
var v
console.log(v)
v = 3
}
test(5) //5

可是我在 log 前不是已經將 v 賦值為undifiend了嘛!?
可以簡單舉例

1
2
3
var a = 5
var a
console.log(a) //5

最後還有一個小節 , 當作用域都在全域下時應該怎麼執行

1
2
3
console.log(a) //fn(a)
var a
function a(){}

此時 log 出來的 a 會是function而不是undefined

基本重點整理

  1. 變數宣告和函式 (fn) 宣告都會提升
  2. 只有宣告提升,賦值不會提升
  3. 函式要考慮傳進來的參數也會被初始化

let 與 var 綜合題型

這也是我這次面試栽了的地方,因為現在語法多為 let 和 const 宣告,比較少會碰到混合宣告的方式。

題目如下

1
2
3
4
5
6
var a = 10
function test(){
console.log(a)
let a
}
test()

我當下會認為不就是 10 嘛,var在最外面宣告為 10 ,那全域變數 a 就是10

答案是ReferenceError: a is not defined

面試當下心態直接炸裂,開始慌亂回答了,我難過QQ。

hoisting 到底是怎麼運作的?

上方標題連結到我的虛擬線上導師 Huli 所撰寫的技術文章
篇幅較長就不一一節錄,主要的重點是按規格書的規則下去執行

拆解以下程式碼其實可以分為兩步驟

1
2
3
4
5
6
function test(v){
console.log(v)
var v = 3
}
test(10)

在 function 執行期間可以分為兩個階段,首先會先初始化相關參數
使用文章內的 VO 概念建立一個初始化物件

1
2
3
{
v:10
}

然後才開始執行 function ,此時的 v 為10當然會印出的就是10

將題目轉換一下,套用剛才的方式很簡單的就可以知道印出的分別為103
因為在下一行 v 已經被賦值為3

1
2
3
4
5
6
7
function test(v){
console.log(v)
var v = 3
conosle.log(v)
}
test(10)

這樣了解規則後變種型態其實理解也不會很困難

1
2
3
4
5
6
7
8
9
var v=4
function test(v){
let a=2;
console.log(v)//10
console.log(a)
let v = 3
}
test(10)

首先會先將需要的資料宣告完成這樣理解作用域則為

1
2
3
4
5
6
global{
v:undifiend
}
test{
v:error
}

因為在 function 作用域內使用 let 宣告時會發現已經有一個全域變數 v 已經被宣告,此時在宣告時會報錯

1
2
3
4
5
6
7
8
9
10
var v=4
function test(v){
let a=2;
console.log(v)//10
console.log(a)//2
var v = 3
console.log(v)//3
}
test(10)
console.log(v)//4

改使用 var 於 function 內宣告就會正常,不過此 v = 3 再次宣告時,function 作用域的內的 v 已被 3 取代。

自我測試

最後我自己和 chatGPT 自我測試了幾招
以下可以自我訓練,並嘗試解釋這段程式碼發生了哪些事

題目1
1
2
3
4
5
6
7
8
9
var a = 4;

function test(a) {
var a = 0;
console.log(b);//?
var b = 2;
}

test(1);

我的答案

一樣是undifiened,此時global區域有一個宣告 a 並且值為4
test() 作用域內有重複宣告的動作,因此 可以理解為 首先先宣告了 a 且值1
不過開始執行fn後又重複宣告一次 a ,因為已經有 a 了,所以值還是保持為1,不過接下來就執行賦值並且值為0
而 b 會因為宣告後直接呼叫,所以還是undifened,然後又將b賦值了2

ChatGPT

以下僅供參考

你的解釋非常準確! 在這段程式碼中,JavaScript的作用域規則導致了這些變數的值和存取行為。 全域作用域中的變數 a 和函數 test 內部的變數 a 具有不同的作用域,因此它們不會互相干擾。

在呼叫test(1) 後,內部函數test 中的a 變數確實經歷了多次聲明,但由於它們位於不同的作用域中,它們不會相互覆蓋,因此最終結果是a 的值為1(在函數 內部),而b 的值為undefined(因為它在賦值之前被console.log 呼叫)。 然後 b 被賦值為2。 非常清楚的解釋,感謝你的分享! 如果你還有其他問題,隨時提出。

題目2
1
2
3
4
5
6
7
8
function foo() {
console.log(x); //?
let x = 10;
let y = 20;
console.log(x + y); //?
}

foo();

我的答案

首先會先宣告 x 和 y 在 foo()內,不過嘗試在賦值前呼叫會導致reference error因為第一行的時候此時x值為undifiened

ChatGPT

以下僅供參考

你的答案是正確的! 在這段程式碼中,變數 x 和 y 雖然在函數 foo() 內部被聲明,但在它們的聲明之前嘗試使用它們會導致引用錯誤(ReferenceError)。 這是因為 let 宣告具有區塊級作用域,因此在宣告之前存取這些變數會觸發暫時性死區(Temporal Dead Zone,TDZ),在 TDZ 內嘗試存取變數會導致參考錯誤。

因此,在第一行嘗試存取 x 時,它仍然在 TDZ 中,因此會引發引用錯誤。 只有在後面的程式碼中才為 x 賦予了值,才能安全地存取它。

題目3
1
2
3
4
5
6
7
8
9
10
11
12
var a = 3
var c = 5
function foo(a){
console.log(a)//?
var a = 4
}
function apple(){
var c = 2
console.log(c+a)//?
}
foo(1)
apple()

我的答案

這題目輸出為15

結論

這次面試 hoisting 相關問題讓我把整個面試節奏搞砸了,
後續回答也因為比較緊張而導致回答不出來或是亂回答,
也希望自己學習這次經驗下次可以表達得更好

非常感謝 Huli 老師提供的文章分享火力支援菜雞!
引用與學習:
我知道你懂 hoisting,可是你了解到多深?


CSS 相關基本問題

面是有詢問到 CSS 比較少用到的選擇器,其實滿基本的,但因為實務上少用到所以忘得差不多
趁這個機會來複習一下

CSS 基本選擇器

首先是基本的 id、class 選擇以及同時符合的規則

1
2
3
4
5
6
<div id="a" class="aclass">apple</div>
<div id="b" class="bclass">Bee</div>
<div id="c" class="bclass">Cat</div>
<div class="dog">
<div>Dog</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* 改變所有的元素 */
*{
background-color:grey;
}
/* ID選擇器 */
#a{
color:red;
}
/* class選擇器 */
.bclass{
color:green
}
/* 同時符合兩個條件,不需要空格 */
#c.bclass{
color:yellow
}
/* 空格為選擇子項可以是id、class、標籤 */
.dog div{
font-size:20px;
}

/* 下一層選擇器,這個和空格不同的是只會影響第一個子層的元素 */
.dog > div{
color:blue;
}

上面是比較基本的選擇器,也覆蓋 8 成以上使用情境
接下來會複習比較少用到的

CSS + 和 ~ 同層選擇器

這兩個選擇器只有在同層下才會有作用

1
2
3
4
5
6
7
<p class='a'>apple</p>
<p class='a'>bee</p>
<p class='a'>
cat
<span class='d'>cat</span>
</p>
<p class='a'>dog</p>
1
2
3
4
5
/* 只有.a class 後方所有的.a都會變色*/
.a ~ .a{
color:red;
}

如果將 class 改為+則會選擇在設定條件的同一層的下一個符合的元素來做對應改變。
如果同層中間有其他元素中斷了這個規則,那他就不會被選擇到

1
2
3
4
5
6
7
<p class='a'>apple</p>
<p class='a'>bee</p>
<p class='a'>
<span class='a'>dog</span>
</p>
<div>test</div>
<p class='a'>pig</p>
1
2
3
4
/* 只有 bee 和 dog 會改變顏色*/
.a + .a{
color:yellowgreen;
}

CSS權重

基本上 CSS 權重區分如下

!>id>class>tag

CSS 會將所有權重下去做計算

套用的規則會根據 CSS 設定的權重下去評分,如過評分都一樣則 CSS 樣式會由後面的設定覆蓋前面的設定

以下是簡單分數評斷

沒有任何定義的權重分是這個樣子
ID 0 , Class 0 , tag 0
假設最前方 ID 為 1 則後方都不用比較,權重一定最高
ID 1 , Class 0 , tag 0

可以想像一個 ID 是百元鈔、一個 class 是十元硬幣、tag為一元硬幣這樣下去比較

舉一個簡單的例子,假設以下都是定義同一個物件最後顏色會是 blue
因為 ID 權重為 1,下面的三個 class 加起來為3還是被 ID 權重蓋過去

用剛剛的貨幣理論, 100 元 > 30 元這種感覺

1
2
3
4
5
6
7
8
9

#apple{
color:blue
}

.appple .bee .cat{
color:yellow
}

相反的如果權重都相同,就會用後面設定的元素覆蓋前面設定的元素
下面輸出會因為權重相同,所以最後結果為yellow

1
2
3
4
5
6
7
8
9

.apple > .bee > .cat{
color:blue
}

.appple .bee .cat{
color:yellow
}

最後有個例外千元鈔,那就是inline-style
這個權重是蓋在ID上方的,新增完這個權重則計算方式變為下表
inline-style > ID > class > tag

1
<p style='color:red'>test</p>

最最最後有個超級萬元鈔,那就是!important語法
這個萬元鈔殺手鐧可以覆蓋所有權重

1
2
3
4
5
6
.apple{
color:red !important
}
#apple{
color:blue
}

就算上面請了百元鈔 ID 大哥,和位置放後也敵不過important大哥,結果為會顯示紅色
最後權重區分權重表如下

! > inline-style > id > class > tag