JavaScript 核心觀念(10)-執行環境與作用域-課後練習

前言

終於來到最後的課後練習,雖然課後練習中絕大部分問題都是我自己出的,但也是很值得回顧的練習,但是這邊會建議不要直接執行程式碼,而是透過觀察畫面來推敲結果答案。

第一題

1
2
3
console.log(a);
var a = 'Hello';
console.log(a);

請問第一個 a 與 第二個 a 答案是什麼?

  1. Hello, undefined
  2. undefined, Hello
  3. not defined, Hello
  4. Hello, not defined
點我看解答(請認真思考後再看解答)

這一題主要考的是 Hoisting 觀念,創造階段變數會被提升,並賦予一個預設值,而這個預設值就是 undefined,因此實際賦予值的階段是在執行階段,所以透過創造階段與執行階段的拆寫就會像這樣

創造階段

1
var a;

執行階段

1
2
3
console.log(a); // undefined
a = 'Hello';
console.log(a); // Hello

因此答案是 「2. undefined, Hello

第二題

1
2
1 = true;
console.log(a);

請問以上程式碼會依序出現什麼錯誤訊息?

  1. LHS, not defined (RHS)
  2. not defined (RHS), LHS
  3. undefined, LHS
  4. not defined (RHS), undefined
點我看解答(請認真思考後再看解答)

這一題主要考的觀念是 Left-Head Side 以及 Right-Head Side

在 JavaScript 中我們無法針對原始型別賦予值,而原始型別有這些

  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • BigInt
  • Stmbol

因此 1 = true; 的 1 是一個 number 故無法賦予值就會出現 LHS 錯誤,而 console.log(a); 則是還未宣告所以無法呼叫所以會出現 RHS。

因此答案是 「1. LHS, not defined (RHS)」。

你也可以試著玩看看以下程式碼是否可以運作?

1
2
3
4
'' = true;
true = '';
null = '';
123 = ture;

但是原始型別中的 undefined 是一個奇妙的存在,因此當你輸入以下程式碼,你會發現都會回傳

1
2
3
undefined = 1;
undefined = '';
undefined = true;

基本上 JavaScript 允許你這麼做,所以不會出現 LHS 錯誤,但實際上千萬不要這麼幹,因為很蠢。

第三題

1
2
3
4
5
6
7
function sayHi() {
var a = 'Mary';
a = 'Tom';
}
var a = 'Casper';
sayHi();
console.log('Hello' + ' ' + a);

請問這個時候的 console 會出現什麼?

  1. Hello Mary
  2. Hello Tom
  3. Hello Casper
  4. 以上皆非
點我看解答(請認真思考後再看解答)

這題答案是 「3. Hello Casper

這一題主要考的是語法作用域的觀念,在程式碼撰寫好時,就已經決定好了作用域,因此在 sayHi 中有重新宣告了 var a = 'Mary'; 因此 a = 'Tom'; 並不會影響到全域變數的 a,所以答案才會是 Hello Casper

第四題

1
2
3
4
5
6
7
8
9
10
11
12
function a() {
console.log('1');
function c() {
console.log('b');
function b() {
console.log('b')
}
b();
}
c();
}
a();

請問當前 JavaScript 的執行順序狀況是如何以及執行堆疊結束時候順序又是如何?

  1. 執行順序:a()c()b(),執行堆疊結束順序:a()b()c()
  2. 執行順序:a()c()b(),執行堆疊結束順序:b()c()a()
  3. 執行順序:c()b()a(),執行堆疊結束順序:a()b()c()
  4. 執行順序:b()a()c(),執行堆疊結束順序:c()a()b()
點我看解答(請認真思考後再看解答)

這一題考的是執行堆與執行堆釋放,在這邊的觀念很簡單,執行堆的執行順序如何,那麼結束時就會反向回來,因此答案是 「2. 執行順序:a()c()b(),執行堆疊結束順序:b()c()a()

第五題

1
2
3
4
5
6
7
8
9
10
11
12
var name;
function a(name) {
console.log('Hello' + ' ' + name);
}

name = 'Tomy';
setTimeout(function() {
name = 'Mary';
a(name);
}, 3000);

name = 'Casper';

請問過三秒後會出現什麼訊息呢?

  1. Hello Tomy
  2. Hello Casper
  3. Hello Mary
  4. Hello undefined
點我看解答(請認真思考後再看解答)

這一題的答案是「3. Hello Mary」

主要考的觀念一樣是語法作用域,因此傳入 name 函式的主要地方是 setTimeout,因為 name 在執行 setTimeout 時,被賦予了 Mary 因此答案是 Hello Mary

第六題

1
2
3
4
5
6
7
8
9
10
function fu() {
if(a) {
console.log('Hello');
} else {
console.log('Oh!');
}
}

fu();
var a = true;

請問 console 會是什麼?

  1. Hello
  2. oh!
  3. 以上皆非
點我看解答(請認真思考後再看解答)

這一題的答案是「2. oh!」

這個問題主要與提升有關係,在創造階段變數會被提升到最前面

1
2
3
4
5
6
7
8
9
// 創造階段
function fu(){
if (a) {
console.log('Hello');
} else {
console.log('oh!');
}
}
var a;

接下來在執行階段時,因為 fu() 優先執行,而 var a 此時還沒有被賦予 true 而是預設值 undefined,因此 if(a) 會是 falseundefined = false),所以才會出現 oh!。

1
2
3
4
5
6
7
8
9
10
11
12
// 執行階段
function fu(){
if (a) { // undefined
console.log('Hello');
} else {
console.log('oh!');
}
}
fu();
console.log(a) // undefined
a = true;
console.log(a) // true

第七題

1
2
3
4
5
function fu() {
console.log(a);
}
var a = 'Hello';
fu();

請問實際程式運作的樣子是什麼(拆解)?

第一種拆解

1
2
3
4
5
6
7
8
// 創造階段
function fu() {
console.log(a);
}
var a;

// 執行階段
a = 'Hello';

第二種拆解

1
2
3
4
5
6
7
8
// 創造階段
function fu() {
console.log(a);
}
a;

// 執行階段
a = 'Hello';

第三種拆解

1
2
3
4
5
6
7
8
// 創造階段
var a = 'Hello';
function fu() {
console.log(a);
}

// 執行階段
fu();

第四種拆解

1
2
3
4
5
6
7
// 創造階段
function fu() {
console.log(a);
}
var a = 'Hello';
// 執行階段
fu();
點我看解答(請認真思考後再看解答)

答案是「第一種拆解」

這一題主要考的是提升觀念,而最主要的重點在於函式比變數有更高的優先權,並且 undefined 會被型轉為 false

第八題

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function youName() {
console.log('你叫什麼?');
}
function sayHi(name) {
console.log('oh!' + ' ' + name + ' ' + '你好!');
}
function myName(name) {
setTimeout(function () {
console.log('我叫' + ' ' + name +'!');
},0)
}

var name = 'Tomy';
youName();
myName(name);
sayHi(name);

請問這個答案會是什麼?

  1. 你叫什麼? → 我叫 Tomy! → oh! Tomy 你好
  2. 你叫什麼? → oh! Tomy 你好! → 我叫 Tomy!
點我看解答(請認真思考後再看解答)

這一題答案是「2. 你叫什麼? → oh! Tomy 你好! → 我叫 Tomy!」

主要考的觀念是非同步觀念,由於 myName 裡面是一個 setTimeout 因此會被放進事件貯列,等後其他動作執行完才會回來看這個事件,因此順序才會是「你叫什麼? → oh! Tomy 你好! → 我叫 Tomy!」

第九題

1
2
3
4
function sayHi(name) {
console.log(name + ' ' + '你好');
}
sayHi() = 'Tomy';

請問這樣出現什麼訊息呢?

  1. Tomy 你好
  2. 不會執行 sayHi()
  3. LHS
  4. undefined 你好
點我看解答(請認真思考後再看解答)

這一題答案是「3. LHS」

這一題主要考的觀念是 Left-Head Side 以及 Right-Head Side

雖然在前面有講過如何原始型別中的 undefined 無法被賦予值之外,function 的名稱也無法被賦予值。

第十題

1
2
3
4
5
6
7
8
9
10
11
function sayHi (a){
var a = 'Mark';
function fu() {
var a = 'Casper';
}
fu();
a = 'HexSchool'
}
var a = 'Mary';
sayHi(a);
console.log(a);

請問當 sayHi() 執行後會出現什麼訊息?

  1. Mary
  2. Mark
  3. Casper
  4. HexSchool
點我看解答(請認真思考後再看解答)

這一題答案是「1. Mary」

這一題主要重點觀念在於「當函式裡面重新宣告一個變數,就不會影響到外部變數」,因為這個 var a 是屬於函式裡面的變數,當函式執行完畢後,因為並沒有回傳變數給予接收,所以記憶體就被釋放了,故答案才會是 Mary,因此這邊另一個重點觀念在於「記憶體釋放」。

所以說 a='HexSchool' 這個 a 是存活在 sayHi 裡面的變數,因此並不會向外尋找另一個 a

參考文獻