我會在我的 Instagram 上發布關於 JavaScript 的複選題,同時也會更新到這個 Repo 當中。更新日期: 2020 年 06 月 12 日
從基礎到進階程度,測試你有多了解 JavaScript,不僅更新你的知識,更能幫助你的 coding 面試! :muscle: :rocket: 我每週都會在這個 Repo 中更新新的題目。
答案在題目下方的摺疊區塊,點擊即可展開答案。祝你好運 ❤️
歡迎在項目中使用它們 😃 我 真的 很感激這個repo的參考,我創造了問題和解釋(是的,我很傷心lol),社區幫助我如此之多地維護和改進它!我很喜歡這個repo。 💪🏼 謝謝你,祝你玩得開心!
function sayHi() {
console.log(name)
console.log(age)
var name = 'Lydia'
let age = 21
}
sayHi()
- A:
Lydia
和undefined
- B:
Lydia
和ReferenceError
- C:
ReferenceError
和21
- D:
undefined
和ReferenceError
答案
在函式內部,我們首先透過 var
關鍵字宣告了 name
變數。這表示變數被提升了(記憶體位置在建立時期就被設置好了),直到程式執行到定義變數的那行之前,預設值都是 undefined
。因為當我們印出 name
變數時,還沒有執行到定義變數的那一行程式碼,因此變數的值保持為 undefined
。
透過 let
和 const
關鍵字宣告的變數也會提升,但是和 var
不同,它們不會被初始化,在我們初始化之前是不能訪問它們的,這個行為被稱之為暫時性死區。當我們嘗試在初始化之前訪問它們時,JavaScript 將會抛出一個 ReferenceError
錯誤。
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1)
}
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1)
}
- A:
0 1 2
和0 1 2
- B:
0 1 2
和3 3 3
- C:
3 3 3
和0 1 2
答案
由於 JavaScript 的事件佇列(Event Queue),setTimeout
的 callback
會在遍歷結束後才執行。因為在第一個迴圈中,遍歷 i
是透過 var
關鍵字宣告的,var
屬於 Function scope(需要用 function() {}
才能將值鎖在作用域裡面)
,所以 for
迴圈會造成變數外流,變成全域變數。在遍歷過程中,我們透過一元運算子 ++
來遞增 i
的值。當 setTimeout
的 callback
執行的時候,i
的值等於 3。
在第二個迴圈中,遍歷 i
是透過 let
關鍵字宣告的:透過 let
和 const
關鍵字的變數擁有塊級作用域(指的是任何在 {}
中的内容)。在每次的遍歷過程中,i
都有一個新值,每次遍歷時 i
值的作用域都在迴圈内。
const shape = {
radius: 10,
diameter() {
return this.radius * 2
},
perimeter: () => 2 * Math.PI * this.radius
}
shape.diameter()
shape.perimeter()
- A:
20
and62.83185307179586
- B:
20
andNaN
- C:
20
and63
- D:
NaN
and63
答案
注意 diameter
的值是一個一般的函式,但是 perimeter
的值是一個箭頭函式。
對於箭頭函式,this
關鍵字指向的是它當前周圍作用域,這個行為和一般函式不同。這表示當我們呼叫 perimeter
時,this
不是指向 shape
物件,而是它的周圍作用域(在範例中是 window
)。
在 window
中沒有 radius
這個屬性,因此回傳 undefined
。
+true;
!"Lydia";
- A:
1
andfalse
- B:
false
andNaN
- C:
false
andfalse
答案
一元運算子加號 +
,嘗試將 boolean 布林值型別轉為 number 數字型別。true
轉為 number 數字型別的話為 1
,false
爲 0
。
字串型別 'Lydia'
是一個真值,我們實際上問的題目是:「這個真值的相反會是什麼?」,真值的相反,將得到 false
。
const bird = {
size: 'small'
}
const mouse = {
name: 'Mickey',
small: true
}
- A:
mouse.bird.size
是無效的 - B:
mouse[bird.size]
是無效的 - C:
mouse[bird["size"]]
是無效的 - D: 以上三個選項都是有效的
答案
在 JavaScript 中,所有物件的 keys 都是字串型別(除非是 Symbol 物件)。儘管我們或許不會定義它們為字串,但它們在底層總會被轉換爲字串。
當我們使用中括號時([]),JavaScript 會解譯語句。它首先看到中括號的第一個開始處 [
並繼續往下直到找到結束的中括號 ]
。只有這樣,它才能計算語句的值。
mouse[bird.size]
:首先計算 bird.size
,這會得到 small
。mouse["small"]
得到 true
。
使用點的語法的時候,上面這一切都不會發生。mouse
沒有 bird
這個 key,這就表示 mouse.bird
是 undefined
。然後當我們使用點語法 mouse.bird.size
時,因為 mouse.bird
是 undefined
,這也就變成了我們實際的語句是 undefined.size
,而此行為是無效的,並會抛出一個錯誤 Cannot read property "size" of undefined
。
let c = { greeting: 'Hey!' }
let d
d = c
c.greeting = 'Hello'
console.log(d.greeting)
- A:
Hello
- B:
undefined
- C:
ReferenceError
- D:
TypeError
答案
在 JavaScript 中,當設定兩個物件彼此相等時,它們會經由*引用(reference)*進行互動。
首先,變數 c
的值是一個物件。接下来,我們將 d
分配了一個和 c
物件相同的引用。
因此當我們改變其中一個物件時,其實是改變了所有的物件的同一個引用的內容。
let a = 3
let b = new Number(3)
let c = 3
console.log(a == b)
console.log(a === b)
console.log(b === c)
- A:
true
false
true
- B:
false
false
true
- C:
true
false
false
- D:
false
true
true
答案
new Number()
是一個内建的函式建構子。它雖然看起來像是個 number,但它實際上並非真正的 number:它有一堆額外的功能,而且它是一個物件。
當我們使用 ==
運算子的時候,它只會檢查兩者是否擁有有相同的值。因為它們的值都是 3
,因此回傳 true
。
然後,當我們使用 ===
運算子時,兩者的值以及型別都必須是相同的。new Number()
是一個物件型別而不是 number(一般型別),因此回傳 false
。
class Chameleon {
static colorChange(newColor) {
this.newColor = newColor
return this.newColor
}
constructor({ newColor = 'green' } = {}) {
this.newColor = newColor
}
}
const freddie = new Chameleon({ newColor: 'purple' })
freddie.colorChange('orange')
- A:
orange
- B:
purple
- C:
green
- D:
TypeError
答案
colorChange
是一個靜態方法。靜態方法被設計爲只能被創造它們的建構子使用(也就是 Chameleon
中的 constructor
),並且不能傳遞給實例。因為 freddie
是一個實例,而靜態方法不能被實例使用,因此會抛出 TypeError
錯誤。
let greeting
greetign = {} // 手殘打錯變數名稱!
console.log(greetign)
- A:
{}
- B:
ReferenceError: greetign is not defined
- C:
undefined
答案
程式碼印出了一個物件,這是因為我們在全域物件上建立了一個空物件!當我們將 greeting
寫錯成 greetign
時,JS 解譯器實際上將它視爲 global.greetign = {}
(或者在瀏覽器中視為 window.greetign = {}
)。
為了避免這個狀況,我們可以使用 "use strict"
,來確保當你宣告變數時,必須賦值。
function bark() {
console.log('Woof!')
}
bark.animal = 'dog'
- A: 正常運作!
- B:
SyntaxError
. 你不能透過這種方式在函式中新增屬性。 - C:
undefined
- D:
ReferenceError
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
const member = new Person("Lydia", "Hallie");
Person.getFullName = function () {
return `${this.firstName} ${this.lastName}`;
}
console.log(member.getFullName());
- A:
TypeError
- B:
SyntaxError
- C:
Lydia Hallie
- D:
undefined
undefined
答案
你可以為一般物件新增屬性,但建構函式(constructor)無法透過上面的方式來新增屬性。若你想一次性在所有實例上都新增某個屬性,要使用原型的方式。因此本例中,使用以下的方式:
Person.prototype.getFullName = function () {
return `${this.firstName} ${this.lastName}`;
}
這樣一來, member.getFullName()
就能有效。這樣做有什麼好處?假設我們真的能如題將這個方法新增到建構函式本身,並不是每個 Person
實例都需要這個方法,但每個實例卻仍然擁有該属性,代表著這將佔據每個實例的記憶體,造成大量的記憶體空間因此浪費掉了。相反,如果我們只將它新增在原型中,那麼它只存在記憶體中的一個位置,而所有實例都可以使用它!
function Person(firstName, lastName) {
this.firstName = firstName
this.lastName = lastName
}
const lydia = new Person('Lydia', 'Hallie')
const sarah = Person('Sarah', 'Smith')
console.log(lydia)
console.log(sarah)
- A:
Person {firstName: "Lydia", lastName: "Hallie"}
andundefined
- B:
Person {firstName: "Lydia", lastName: "Hallie"}
andPerson {firstName: "Sarah", lastName: "Smith"}
- C:
Person {firstName: "Lydia", lastName: "Hallie"}
and{}
- D:
Person {firstName: "Lydia", lastName: "Hallie"}
andReferenceError
答案
對 sarah
而言,我們沒有使用 new
關鍵字。當使用 new
時,this
引用我們建立的空物件。沒有使用 new
的時候,this
引用的是全域物件(global object)。
我們會說 this.firstName
等於 "Sarah"
,而 this.lastName
等於 "Smith"
。實際上我們做的是,定義了 global.firstName = 'Sarah'
和 global.lastName = 'Smith'
。而 sarah
本身是 undefined
,因為 Person
這個函式本身並沒有回傳值。
- A: Target > Capturing > Bubbling
- B: Bubbling > Target > Capturing
- C: Target > Bubbling > Capturing
- D: Capturing > Target > Bubbling
- A: true
- B: false
答案
除了基本物件(base object,使用 new
關鍵字建立的物件)以外,所有物件都有原型。基本物件可以使用一些方法和属性,比如 .toString
,這就是為什麼你可以使用內建的 JavaScript 方法!所有這類在原型上的方法都是可被使用的。雖然 JavaScript 不能直接在物件上找到這些方法,但 JavaScript 會沿著原型鍊找到它們,以便使用。
function sum(a, b) {
return a + b
}
sum(1, '2')
- A:
NaN
- B:
TypeError
- C:
"12"
- D:
3
答案
JavaScript 是一個動態型別語言:我們不指定變數的型別。值可以在你不知道的情况下自動轉換成另一種型別,稱為隱含式轉型(implicit type coercion)。Coercion 是指將一種型別轉換成另一種型別。
在此範例中,JavaScript 將數字型別 1
轉換為字串型別,以便函式能回傳一個有意義的值。數字型別(1
)和字串型別('2'
)相加的時候,該數字會被視為字串。我們也能連接不同的字串,比如 "Hello" + "World"
,而此例是 "1" + "2"
,它將回傳 "12"
。
let number = 0;
console.log(number++);
console.log(++number);
console.log(number);
- A:
1
1
2
- B:
1
2
2
- C:
0
2
2
- D:
0
1
2
答案
一元運算子 ++
加在變數後方:
- 回傳值 (這個值是
0
) - 新增值 (變數
number
的值現在是1
)
一元運算子 ++
加在變數前方:
- 新增值 (變數
number
的值現在是2
) - 回傳值 (這個值是
2
)
因此答案是 0 2 2
.
function getPersonInfo(one, two, three) {
console.log(one);
console.log(two);
console.log(three);
}
const person = 'Lydia';
const age = 21;
getPersonInfo`${person} is ${age} years old`;
- A:
"Lydia"
21
["", " is ", " years old"]
- B:
["", " is ", " years old"]
"Lydia"
21
- C:
"Lydia"
["", " is ", " years old"]
21
答案
若你使用標籤樣板字面值(Tagged template literals),第一個參數的值永遠會是一個裝載字串的陣列,函式中的剩下的參數會取得表達式中傳進的變數(person
、age
)的值('Lydia'
、21
)!
function checkAge(data) {
if (data === { age: 18 }) {
console.log('You are an adult!');
} else if (data == { age: 18 }) {
console.log('You are still an adult.');
} else {
console.log(`Hmm.. You don't have an age I guess`);
}
}
checkAge({ age: 18 });
- A:
You are an adult!
- B:
You are still an adult.
- C:
Hmm.. You don't have an age I guess
答案
驗證相等,一般型別只比較「值」,物件型別則是比較他們的「參考」。JavaScript 會確認不同物件的參考是否指向同一個記憶體位置。
題目中,我們比較的兩個物件擁有不同的記憶體位置:一個物件是作為參數傳遞的物件,它的記憶體位置與另一個拿來判斷是否相等的物件並不相同。
這就是 { age: 18 } === { age: 18 }
與 { age: 18 } == { age: 18 }
會回傳 false
的原因.
function getAge(...args) {
console.log(typeof args);
}
getAge(21);
- A:
"number"
- B:
"array"
- C:
"object"
- D:
"NaN"
function getAge() {
'use strict';
age = 21;
console.log(age);
}
getAge();
- A:
21
- B:
undefined
- C:
ReferenceError
- D:
TypeError
答案
"use strict"
可以避免妳意外地宣告全區域變數。使用 "use strict"
時,我們若沒有宣告 age
這個變數,就直接賦值的話,會拋出 ReferenceError
,若沒有使用 "use strict"
,屬性 age
就會新增到全域物件上(瀏覽器上的全域物件是 window
)。
const sum = eval('10*10+5');
- A:
105
- B:
"105"
- C:
TypeError
- D:
"10*10+5"
sessionStorage.setItem('cool_secret', 123);
- A: 永遠都可以,資料不會不見。
- B: 使用者關閉頁籤後,資料才會失效。
- C: 使用者要關閉一整個瀏覽器,資料才會失效,單純關閉資料不會造成資料消失。
- D: 使用者關閉電腦後。
答案
儲存在 sessionStorage
的資料會在使用者「關閉頁籤」後消失。
若是使用 localStorage
來儲存資料的話,資料則會永遠存在瀏覽器端,直到觸發了 localStorage.clear()
才可以清除資料。
var num = 8;
var num = 10;
console.log(num);
- A:
8
- B:
10
- C:
SyntaxError
- D:
ReferenceError
答案
使用 var
關鍵字重複宣告的變數,該值會以最新賦予的值作為它的「值」。這件事情在 let
or const
不會發生,因為這兩個關鍵字所宣告的變數作用域是塊級作用域(block-scoped)。
const obj = { 1: 'a', 2: 'b', 3: 'c' };
const set = new Set([1, 2, 3, 4, 5]);
obj.hasOwnProperty('1');
obj.hasOwnProperty(1);
set.has('1');
set.has(1);
- A:
false
true
false
true
- B:
false
true
true
true
- C:
true
true
false
true
- D:
true
true
true
true
答案
除了 Symbol
以外的物件的 key
在底層都是字串類別,即使你建立該物件屬性時,並不是以字串來建立的,所以 obj.hasOwnProperty('1')
會回傳 true
。不過 set
不是這樣的規則,在題目中的 set
並沒有字串 '1'
這個屬性名稱,所以 set.has('1')
會回傳 false
,不過是有數字類別 1
的屬性值,set.has(1)
將會回傳 true
。
const obj = { a: 'one', b: 'two', a: 'three' };
console.log(obj);
- A:
{ a: "one", b: "two" }
- B:
{ b: "two", a: "three" }
- C:
{ a: "three", b: "two" }
- D:
SyntaxError
- A: true
- B: false
- C: it depends
for (let i = 1; i < 5; i++) {
if (i === 3) continue;
console.log(i);
}
- A:
1
2
- B:
1
2
3
- C:
1
2
4
- D:
1
3
4
String.prototype.giveLydiaPizza = () => {
return 'Just give Lydia pizza already!';
};
const name = 'Lydia';
name.giveLydiaPizza();
- A:
"Just give Lydia pizza already!"
- B:
TypeError: not a function
- C:
SyntaxError
- D:
undefined
答案
String
是內建的建構函式,我們可以向它新增属性。我只是在它的原型中加上一個方法。基本型別字串被自動轉換成字串物件,由字串原型函式生成。因此,所有 string(string 物件)都可以使用 giveLydiaPizza
方法!
const a = {};
const b = { key: 'b' };
const c = { key: 'c' };
a[b] = 123;
a[c] = 456;
console.log(a[b]);
- A:
123
- B:
456
- C:
undefined
- D:
ReferenceError
答案
物件的 key 自動轉為字串型別。我們正嘗試將物件 b
的 key 設為物件 a
的 key,其值爲 123
。
然而,當物件「字串化」,它會變成 "[object Object]"
。所以這裡的意思是,a["[object Object]"] = 123
。然後,我們又再做了一次一樣的事情,c
也是隱式的物件字串化,所以,a["[object Object]"] = 456
。
最後,我們輸出 a[b]
,也就是 a["[object Object]"]
。之前剛賦值爲 456
,將回傳 456
。
const foo = () => console.log('First');
const bar = () => setTimeout(() => console.log('Second'));
const baz = () => console.log('Third');
bar();
foo();
baz();
- A:
First
Second
Third
- B:
First
Third
Second
- C:
Second
First
Third
- D:
Second
Third
First
答案
我們有一個 setTimeout
函式,首先呼叫它。然而,它的執行順序是最後執行的。
因為在瀏覽器中,我們除了有執行引擎,還有一個 WebAPI
。WebAPI
提供了 setTimeout
函式,也包含其他的,例如 DOM。
在『callback』推送到 WebAPI
後,setTimeout
函式本身(不是回呼函式)將從堆疊(stack
)中彈出。
現在,foo
被呼叫,印出 "First"
。
foo
從堆疊中彈出,baz
被呼叫,印出 "Third"
。
WebAPI 不能隨時向堆疊内新增内容。相反,它會將回呼函式彈到名爲『queue
』的地方。
這就是事件迴圈(Event Loop
)的流程,了解事件迴圈堆疊與任務佇列的運作模式。如果堆疊是空的,它接受任務佇列上的第一个元素,推入堆疊中。
bar
被呼叫,印出 "Second"
,然後它被彈出堆疊。
<div onclick="console.log('first div')">
<div onclick="console.log('second div')">
<button onclick="console.log('button')">
Click!
</button>
</div>
</div>
- A: 第一層的
div
- B: 第二層的
div
- C:
button
本身 - D: 一個包含此巢狀元件的陣列.
<div onclick="console.log('div')">
<p onclick="console.log('p')">
Click here!
</p>
</div>
- A:
p
div
- B:
div
p
- C:
p
- D:
div
答案
輸出內容是 p
及 div
。在事件傳播(event propagation) 期間,分為三個階段:捕獲(capturing),目標(target) 和冒泡(bubbling)。
預設情況下,事件處理(event handlers) 在冒泡階段執行(除非您將useCapture設置為true)。 它從巢狀元素的最深層向外層。
const person = { name: 'Lydia' };
function sayHi(age) {
return `${this.name} is ${age}`;
}
console.log(sayHi.call(person, 21));
console.log(sayHi.bind(person, 21));
- A:
undefined is 21
Lydia is 21
- B:
function
function
- C:
Lydia is 21
Lydia is 21
- D:
Lydia is 21
function
答案
通過 .call
及 .bind
,我們可以將想要 this
關鍵字引用的物件傳遞給它。
然而,.call
會 立即執行! .bind.
則是會回傳一份函式(function)的 複製 且不會立即執行。
function sayHi() {
return (() => 0)();
}
console.log(typeof sayHi());
- A:
"object"
- B:
"number"
- C:
"function"
- D:
"undefined"
答案
sayHi
函數會回傳立即執行函式表示式(IIFE)的回傳值。 該函數回傳類型為 "number"
的 0
。
FYI: JS只有7種原生類型(type) : null
, undefined
, boolean
, number
, string
, object
, symbol
, 和 bigint
. "function"
不是一種類型而是物件。
0;
new Number(0);
('');
(' ');
new Boolean(false);
undefined;
- A:
0
,''
,undefined
- B:
0
,new Number(0)
,''
,new Boolean(false)
,undefined
- C:
0
,''
,new Boolean(false)
,undefined
- D: All of them are falsy
答案
只有八個值是 falsy
undefined
null
NaN
false
''
(空字串)0
-0
0n
(BigInt(0))
函式建構式(Function constructors) 如 new Number
和 new Boolean
都為 truthy。
console.log(typeof typeof 1);
- A:
"number"
- B:
"string"
- C:
"object"
- D:
"undefined"
const numbers = [1, 2, 3];
numbers[10] = 11;
console.log(numbers);
- A:
[1, 2, 3, 7 x null, 11]
- B:
[1, 2, 3, 11]
- C:
[1, 2, 3, 7 x empty, 11]
- D:
SyntaxError
答案
當您設置的元素其位置大過陣列長度時,JavaScript 會建立一個叫做 "empty slots" 的物件, 它們的值實際上為 undefined
。
但您會看到類似的輸出內容 : [1, 2, 3, 7 x empty, 11]
。實際執行環境會使其輸出內容略微不同 (瀏覽器, node... 等)
(() => {
let x, y;
try {
throw new Error();
} catch (x) {
(x = 1), (y = 2);
console.log(x);
}
console.log(x);
console.log(y);
})();
- A:
1
undefined
2
- B:
undefined
undefined
undefined
- C:
1
1
2
- D:
1
undefined
undefined
答案
程式中的 catch
區塊捕獲了一個例外情況且賦殖予 argument x
。這個 x
是在區塊內產生的,其有效範圍只在區塊內(block-scoped),它跟 console.log
中所傳入的 x
並不是同一個。
接著我們將此區塊變數 x
設置為等於 1
,並設置變數 y
的值, 現在我們 console.log 區塊變數 x
,無意外地它輸出 1
。
而在 catch
區塊之外的 x
仍然是 undefined
且 y
是 2
。 因此當我們想在 catch
區塊之外使用 console.log(x)
時,它回傳 undefined
,而 y
回傳 2
。
- A: JavaScript 的世界中不是 primitive 就是 object
- B: JavaScript 的世界中不是 function 就是 object
- C: JavaScript 的世界中只有 object
- D: JavaScript 的世界中不是 number 就是 object
答案
JavaScript 只有 primitive types 和 objects.
而 Primitive types 包含 boolean
, null
, undefined
, bigint
, number
, string
, 和 symbol
.
Primitive 不同於 object 的是它沒有任何的屬性(properties) 和方法(methods); 沒有方法的情況下為何 'foo'.toUpperCase()
(string) 是輸出 'FOO'
而不是 TypeError
?
這是因為當您嘗試訪問 primitive types (例如字串) 的屬性或方法時,JavaScript會使用其中一個 wrapper classes 包裝該 primitive type。
例如使用了 String
包裝 primitive type string
, 接著在 expression 被 evaluates 後拋棄該包裝。 所有 primitives 除了 null
和 undefined
外都是遵循此行為。
[[0, 1], [2, 3]].reduce(
(acc, cur) => {
return acc.concat(cur);
},
[1, 2],
);
- A:
[0, 1, 2, 3, 1, 2]
- B:
[6, 1, 2]
- C:
[1, 2, 0, 1, 2, 3]
- D:
[1, 2, 6]
答案
[1, 2]
為初始值,同時也是第一個 acc
。 在第一輪中, acc
是 [1,2]
且 cur
是 [0, 1]
,兩陣列連接後的結果是 [1, 2, 0, 1]
。
接著 [1, 2, 0, 1]
是 acc
且 [2, 3]
是 cur
,兩陣列連接後的結果是 [1, 2, 0, 1, 2, 3]
。
!!null;
!!'';
!!1;
- A:
false
true
false
- B:
false
false
true
- C:
false
true
true
- D:
true
true
false
答案
null
是 falsy. !null
回傳 true
. !true
回傳 false
.
""
是 falsy. !""
回傳 true
. !true
回傳 false
.
1
是 truthy. !1
回傳 false
. !false
回傳 true
.
setInterval(() => console.log('Hi'), 1000);
- A: 一個唯一的 id
- B: 指定的毫秒數
- C: 被傳遞的函式
- D:
undefined
[...'Lydia'];
- A:
["L", "y", "d", "i", "a"]
- B:
["Lydia"]
- C:
[[], "Lydia"]
- D:
[["L", "y", "d", "i", "a"]]
答案
字串(string) 類別是可以被迭代的(iterable), 展開運算子(spread operator) 將可迭代的字元(character) 映射(map) 置一個元素(element) 上。
function* generator(i) {
yield i;
yield i * 2;
}
const gen = generator(10);
console.log(gen.next().value);
console.log(gen.next().value);
- A:
[0, 10], [10, 20]
- B:
20, 20
- C:
10, 20
- D:
0, 10 and 10, 20
答案
一般函式不能在被呼叫後中途停止。但是, generator 可以在中途 "停止" 且之後可以從停止的位置繼續執行。
每當一個 generator 函式遇到一個 yield
關鍵字時,該函式就會產生其後指定的值。 請注意,在這種情況下,generator 函式不是 return 值,而是 yields 值。
首先,我們使用等於 "10" 的 "i" 初始化 generator 函式。 我們使用 "next()" 方法呼叫 generator 函式。 第一次呼叫 generator 函式時, "i" 等於 "10"。
它遇到第一個 yield
關鍵字:它產生 i
的值。 現在,generator 已 "暫停", 並且記錄了 "10"。
然後,我們使用 next()
方法再次呼叫該函式。 它將從先前停止的地方繼續,仍然是 "i" 等於 "10"。 現在,它遇到下一個 yield
關鍵字,並產生 i * 2
。
"i" 等於 "10",因此回傳 "10 * 2",即 "20"。 故結果為10、20。
const firstPromise = new Promise((res, rej) => {
setTimeout(res, 500, 'one');
});
const secondPromise = new Promise((res, rej) => {
setTimeout(res, 100, 'two');
});
Promise.race([firstPromise, secondPromise]).then(res => console.log(res));
- A:
"one"
- B:
"two"
- C:
"two" "one"
- D:
"one" "two"
答案
當我們向 Promise.race 方法傳遞多個 promise 時,它將 resolves / rejects 最先的 promise。
在 setTimeout 方法中,我們傳遞了一個計時器:第一個 promise(firstPromise)為500毫秒,第二個 promise(secondPromise)為100毫秒。 這意味著 "secondPromise" 將先用 "two" 的值進行resolves。 現在, res
擁有 'two' 的值且該值被 console.log。
let person = { name: 'Lydia' };
const members = [person];
person = null;
console.log(members);
- A:
null
- B:
[null]
- C:
[{}]
- D:
[{ name: "Lydia" }]
答案
首先,我們宣告一個物件變數 person
包含 name
屬性以及值 Lydia
。
接著我們宣告另一個陣列變數 members
。我們將該陣列的第一個元素設置等於 person
變數的值。
當我們將它們設置為相等時,物件透過 reference 互相關聯。當我們將一個物件變數的 reference 賦值給另一個變數時,實際上我們是 複製 該 reference (它們沒有 相同 的 reference !)
接著我們將變數 person
賦予 null
。
我們僅修改變數 person
的值,並無修改陣列中的第一個元素。
基於該元素有份不同的 reference (一份複製的),故 members
陣列中第一位元素仍保有對物件的指向,於是當我們 console.log members
陣列時,輸出內容為物件。
const person = {
name: 'Lydia',
age: 21,
};
for (const item in person) {
console.log(item);
}
- A:
{ name: "Lydia" }, { age: 21 }
- B:
"name", "age"
- C:
"Lydia", 21
- D:
["name", "Lydia"], ["age", 21]
答案
通過 for-in
循環,我們可以遍歷物件的鍵,在這個題目中的鍵是 name
和 age
。 在內部,物件鍵是字串(strings)(如果它們不是 Symbol)。
在每次循環中,我們將 item
的值設置為等於其迭代的當前鍵。 第一輪循環中,item
等於 name
,並輸出內容。 接著, item
等於 age
,並輸出內容。
console.log(3 + 4 + '5');
- A:
"345"
- B:
"75"
- C:
12
- D:
"12"
答案
運算子關聯性是編譯器計算表達式的順序,從左到右或從右到左。僅適用於所有運算子具有 相同 優先級時,才會發生這種情況。 在這裡我們只有一種類型的運算子:+。 而其關聯性是從左到右。
首先計算 3 + 4
。 結果為數字7。
由於強制(coercion) ,7 +'5'
會導致結果為 75
。JavaScript將數字 7
轉換型態成字串,請參閱問題15。我們可以使用 +
運算子將兩個字串連接起來。 7
+ 5
產生 75
。
const num = parseInt('7*6', 10);
- A:
42
- B:
"42"
- C:
7
- D:
NaN
答案
僅會回傳字串中的第一個數字。 基於 radix (第二個參數,用於指定我們要將其解析為哪種類型的數字:以10為基數,十六進制,八進制,二進制等),parseInt
檢查字串中的字元是否有效。
一旦遇到基數中無效數字的字元,它將停止解析並忽略以下字元。
*
不是合法的 number
,所以程式僅將字串形態的 "7"
轉換至 decimal 形態的 7
,故 num
現在的值為 7
。
[1, 2, 3].map(num => {
if (typeof num === 'number') return;
return num * 2;
});
- A:
[]
- B:
[null, null, null]
- C:
[undefined, undefined, undefined]
- D:
[ 3 x empty ]
答案
當對陣列做映射(map) 時,num
的值等同於它當前正在循環的元素。在這種情況中元素均為 numbers,所以條件式 typeof num === "number"
會回傳 true
的值。
map 函式會建立一個新陣列,並插入該函式回傳的值。
但是我們不回傳任何值。當我們不從函式回傳值時,函式將回傳 undefined
。由於陣列中的每個元素都會呼叫該函式,因此對於每個元素,我們都回傳 undefined
。
function getInfo(member, year) {
member.name = 'Lydia';
year = '1998';
}
const person = { name: 'Sarah' };
const birthYear = '1997';
getInfo(person, birthYear);
console.log(person, birthYear);
- A:
{ name: "Lydia" }, "1997"
- B:
{ name: "Sarah" }, "1998"
- C:
{ name: "Lydia" }, "1998"
- D:
{ name: "Sarah" }, "1997"
答案
參數是透過 value 傳遞,除非它們是一個物件(object),物件則由透過 reference 傳遞。 birthYear
是透過值傳遞的,因為它是字串不是物件。 當我們按值傳遞參數時,將建立該值的 copy (請參閱問題46)。
變數 birthYear
具有對值 1997
的 reference。參數 year
也有對值 1997
的 reference,但與變數 birthYear
所 reference 的不同。
因此當我們通過將 year
設置為等於 1998
來更新 year
的值時,我們僅更新了 year
的值。 birthYear
仍然等於 "1997"
。
person
的值是一個物件。 參數 member
具有(複製的)reference 指向 相同 物件。
因此當我們修改物件 member
的屬性時, person
的值也會被修改,因為它們都 reference 了相同的物件。 person
的 name
屬性現在等於值 "Lydia"
。
function greeting() {
throw 'Hello world!';
}
function sayHi() {
try {
const data = greeting();
console.log('It worked!', data);
} catch (e) {
console.log('Oh no an error:', e);
}
}
sayHi();
- A:
It worked! Hello world!
- B:
Oh no an error: undefined
- C:
SyntaxError: can only throw Error objects
- D:
Oh no an error: Hello world!
答案
使用 throw
語句,我們可以建立自定義的錯誤。 使用此語句,您可以觸發例外(exception)。例外可以是 <b>string</ b>
,<b>number</ b>
,<b>boolean</ b>
或 <b>object</ b>
。
在這種情況下,我們的例外是字串 Hello world
。
通過 catch
語句,我們可以指定如果在 try
的程式區塊中拋出例外時該怎麼辦。 例如拋出例外:字串 'Hello world'
。
現在, e
等於我們記錄的字串。 因此輸出結果將會是 'Oh an error: Hello world'
。
function Car() {
this.make = 'Lamborghini';
return { make: 'Maserati' };
}
const myCar = new Car();
console.log(myCar.make);
- A:
"Lamborghini"
- B:
"Maserati"
- C:
ReferenceError
- D:
TypeError
答案
當您回傳屬性(property) 時,該屬性的值等於 returned 的值,而不是在函式建構式(constructor function)中設置的值。 我們回傳字串 Maserati
,因此 mycar.make
等於 Maserati
。
(() => {
let x = (y = 10);
})();
console.log(typeof x);
console.log(typeof y);
- A:
"undefined", "number"
- B:
"number", "number"
- C:
"object", "number"
- D:
"number", "undefined"
答案
let x = y = 10;
實際上是 shorthand for:
y = 10;
let x = y;
當我們將 y
設置為等於 10
時,我們實際上將屬性 y
加入到 global object 中(瀏覽器中的 window
,Node中的 global
)。 現在,瀏覽器中 window.y
現在等於 10
。
接著我們宣告一個變數 x
,並將其值賦予為 y
,即 10
。 用 let
關鍵字宣告的變數是 block scoped ,它們僅在宣告它們的區塊中定義; 另外此案例的函示是,立即函示表達式(IIFE)。
當我們使用 typeof
運算子時, x
並未被定義:我們試圖在宣告它的區塊外訪問 x
。這將獲得 x
並未被定義的結果。 未分配值或未宣告的值的類型為 "undefined"
。 console.log(typeof x)
回傳 "undefined"
。
但是,當將 y
設置為 10
時,我們建立了global variable y
。 在我們程式中的任何位置均可訪問此值。
y
被定義,並且為類型 number
的值。 因此 console.log(typeof y
回傳 "number"
。
class Dog {
constructor(name) {
this.name = name;
}
}
Dog.prototype.bark = function() {
console.log(`Woof I am ${this.name}`);
};
const pet = new Dog('Mara');
pet.bark();
delete Dog.prototype.bark;
pet.bark();
- A:
"Woof I am Mara"
,TypeError
- B:
"Woof I am Mara"
,"Woof I am Mara"
- C:
"Woof I am Mara"
,undefined
- D:
TypeError
,TypeError
答案
透過 delete
關鍵字,我們可以從物件中刪除它的屬性。同樣適用在原型(prototype)。通過刪除原型上的屬性,該屬性在原型鏈中將不可再被使用。
在這個案例中, bark
函式在 delete Dog.prototype.bark
之後的原型上不再可用,但是我們仍然嘗試訪問它。
因此當我們嘗試呼叫不是函式的東西時,程式將拋出 TypeError
。 在這個案例中,將為 TypeError: pet.bark is not a function
,因為 pet.bark
是 undefined
。
const set = new Set([1, 1, 2, 3, 4]);
console.log(set);
- A:
[1, 1, 2, 3, 4]
- B:
[1, 2, 3, 4]
- C:
{1, 1, 2, 3, 4}
- D:
{1, 2, 3, 4}
答案
Set
物件是 唯一 值的集合: 任何存在於 Set
的值均為唯一的,不會存在相同的值(重複的值將會由後出現的覆蓋已出現的)。
陣列 [1, 1, 2, 3, 4]
中有重複的值 1
,因此結果會是 {1, 2, 3, 4}
。
// counter.js
let counter = 10;
export default counter;
// index.js
import myCounter from './counter';
myCounter += 1;
console.log(myCounter);
- A:
10
- B:
11
- C:
Error
- D:
NaN
答案
被引用(imported) 的模組(module) 是 唯讀 的: 您無法修改被引用模組中項目,只有輸出(export) 該項目的模組可以更改它的值。
因此當我們嘗試增加 myCounter
的值時,他將拋出錯誤: myCounter
is read-only and cannot be modified。
const name = 'Lydia';
age = 21;
console.log(delete name);
console.log(delete age);
- A:
false
,true
- B:
"Lydia"
,21
- C:
true
,true
- D:
undefined
,undefined
答案
delete
運算子會回傳一個布林值: 成功刪除物件的情況下會回傳 true
,反之則為 false
。 但是經由 var
,const
或是 let
關鍵字所宣告的變數是無法使用 delete
運算子刪除的。
此處, name
無法成功刪除且會回傳 fasle
,因為它是經由 const
所宣告。當我們宣告 age
的值為 21
時,實際上我們做的是將一個名為 age
的屬性為添加到了全球物件中,您可以透過 delete
來刪除物件中的屬性,因此您也能刪除全球物件中的屬性,故將回傳 true
。
const numbers = [1, 2, 3, 4, 5];
const [y] = numbers;
console.log(y);
- A:
[[1, 2, 3, 4, 5]]
- B:
[1, 2, 3, 4, 5]
- C:
1
- D:
[1]
答案
我們可以通過解構(destructuring) 從陣列或物件的屬性中獲得值。例如:
[a, b] = [1, 2];
a 的值現在為 1
且b 的值現在為2
。我們針對此問題所做的動作為:
[y] = [1, 2, 3, 4, 5];
這代表著 y
的值等同於陣列中第一個元素的值,即為 1
。因此我們執行 console.log(y)
時, 1
將被輸出。
const user = { name: 'Lydia', age: 21 };
const admin = { admin: true, ...user };
console.log(admin);
- A:
{ admin: true, user: { name: "Lydia", age: 21 } }
- B:
{ admin: true, name: "Lydia", age: 21 }
- C:
{ admin: true, user: ["Lydia", 21] }
- D:
{ admin: true }
答案
使用 spread 運算子可以合併物件(...
)。它使您可以建立一個物件的鍵/值的複製,並將其添加到另一物件中。
在這裡我們建立了 user
物件的複製並將其添加至 admin
物件。因此將輸出 { admin: true, name: "Lydia", age: 21 }
。
const person = { name: 'Lydia' };
Object.defineProperty(person, 'age', { value: 21 });
console.log(person);
console.log(Object.keys(person));
- A:
{ name: "Lydia", age: 21 }
,["name", "age"]
- B:
{ name: "Lydia", age: 21 }
,["name"]
- C:
{ name: "Lydia"}
,["name", "age"]
- D:
{ name: "Lydia"}
,["age"]
答案
透過 defineProperty
,我們可以對物件增加新的屬性或是修改已經存在的屬性。當我們使用 defineProperty
增加物件的屬性時,它們被預設為 不可 enumerable 。
Object.keys
方法僅回傳物件中所有 可 enumerable 的屬性名稱,這個案例中只有 "name"
。
預設下,使用 defineProperty
方法增加的屬性是不可變的。但您可以覆蓋這個行為透過 writable
,configurable
及 enumerable
屬性。
於是,defineProperty
方法可以使您對要增加到物件的屬性進行更多的控制。
const settings = {
username: 'lydiahallie',
level: 19,
health: 90,
};
const data = JSON.stringify(settings, ['level', 'health']);
console.log(data);
- A:
"{"level":19, "health":90}"
- B:
"{"username": "lydiahallie"}"
- C:
"["level", "health"]"
- D:
"{"username": "lydiahallie", "level":19, "health":90}"
答案
JSON.stringify
的第二個參數是 替換者 (replacer) ,替換者可以是函式,也可以是陣列,並允許您控制值要如何獲怎麼串化(stringified)。
如果替換者是 陣列 ,僅將陣列中包含的屬性名稱加到 JSON 字串中。
此案例中,僅有 "level"
and "health"
被包含,"username"
沒有被包含在內,因此 data
的值將為 "{"level":19, "health":90}"
。
如果替換者是 函式 ,在要字串化的每個物件屬性上將會呼叫此函式。從此函式回傳的值將是加到 JSON 字串中的屬性的值。如果值為 undefined
,則此屬性從 JSON 字串中排除。
let num = 10;
const increaseNumber = () => num++;
const increasePassedNumber = number => number++;
const num1 = increaseNumber();
const num2 = increasePassedNumber(num1);
console.log(num1);
console.log(num2);
- A:
10
,10
- B:
10
,11
- C:
11
,11
- D:
11
,12
答案
單元運算子 ++
首先 回傳 操作數的值,然後 遞增 操作數的值。 num1
的值是 10
,因為 increaseNumber
函式首先回傳 num
的值,即 10
,之後才遞增 num
的值。
num2
是 10
, 因為我們將 num1
傳遞給了 increasePassedNumber
。 number
等於 10
( num1
的值。同樣,單元運算子 ++
首先 回傳 操作數的值,然後 遞增 操作數的值。
number
的值是 10
,因此 num2
等於 10
。
const value = { number: 10 };
const multiply = (x = { ...value }) => {
console.log((x.number *= 2));
};
multiply();
multiply();
multiply(value);
multiply(value);
- A:
20
,40
,80
,160
- B:
20
,40
,20
,40
- C:
20
,20
,20
,40
- D:
NaN
,NaN
,20
,40
答案
在ES6中,我們可以使用預設值初始化參數。如果沒有其他值傳遞給該函式或是傳入的參數是 undefined
,則該參數的值為預設值。此案例中,我們將 value
物件的屬性擴展到一個新物件中,因此 x
具有預設值 {number:10}
。
預設值是在 呼叫 時被 evaluated。每次呼叫該函式時,都會建立一個 新 物件。我們在沒有傳遞值的情況下呼叫了 multiply
函式兩次:x
的預設值是 {{number:10}
。因此,我們輸出該數字的相乘值,即 20
。
第三次呼叫時,我們確實傳遞了一個參數:名為 value
的物件。 *=
運算子實際上是 x.number = x.number * 2
的簡寫:因此我們修改了 x.number
的值,並記錄相乘後的值 20
。
第四次,我們再次傳遞名為 value
的物件。 x.number
先前已修改為 20
,因此 x.number * = 2
為 40
。
[1, 2, 3, 4].reduce((x, y) => console.log(x, y));
- A:
1
2
and3
3
and6
4
- B:
1
2
and2
3
and3
4
- C:
1
undefined
and2
undefined
and3
undefined
and4
undefined
- D:
1
2
andundefined
3
andundefined
4
答案
reduce
方法接收的第一個參數是 累加器(accumulator) ,在這種情況下是 x
。 第二個參數是 current value y
。 使用 reduce
方法,我們對陣列中的每個元素執行一個 callback 函式,並在最終回一個值。
在此示例中,我們不回傳任何值,僅記錄了累加器的值和當前值。
累加器的值等於 callback 函式先前回傳的值。 如果沒有 initialValue
參數傳遞給 reduce
方法,則累加器的初始值將會等於第一個元素。
在第一個呼叫中,累加器(x
)為1
,當前值(y
)為2
。 我們不從 callback 函式回傳,而是輸出累加器和當前值:1
和 2
。
如果您不從 callback 函式回傳值,則它將回傳 undefined
。 在下一次呼叫時,累加器為 undefined
,當前值為 3
。 於是 undefined
和 3
被輸出。
在第四次呼叫中,我們再次不從 callback 函式回傳。 累加器再次為 undefined
,當前值為 4
。於是 undefined
和 4
被輸出。
class Dog {
constructor(name) {
this.name = name;
}
};
class Labrador extends Dog {
// 1
constructor(name, size) {
this.size = size;
}
// 2
constructor(name, size) {
super(name);
this.size = size;
}
// 3
constructor(size) {
super(name);
this.size = size;
}
// 4
constructor(name, size) {
this.name = name;
this.size = size;
}
};
- A: 1
- B: 2
- C: 3
- D: 4
答案
在子類別中,在呼叫 super
前不能存取 this
關鍵字,如果你這麼做,它將拋出一個 ReferenceError
,建構式1與4會引發這個錯誤。
使用 super
關鍵字時,我們要提供參數給父類別呼叫其建構式。父類別需要接受一個 name
參數,所以我們需要把 name
傳給 super
。
Labrador
類別接收兩個參數, name
參數是由於它繼承了 Dog
, size
作為Labrador
類的額外屬性,它們都需要傳遞給 Labrador
的建構式,因此使用建構式2是正確答案。
// index.js
console.log('running index.js');
import { sum } from './sum.js';
console.log(sum(1, 2));
// sum.js
console.log('running sum.js');
export const sum = (a, b) => a + b;
- A:
running index.js
,running sum.js
,3
- B:
running sum.js
,running index.js
,3
- C:
running sum.js
,3
,running index.js
- D:
running index.js
,undefined
,running sum.js
答案
import
命令是 編譯階段 執行的。這代表被引入的模組會優先執行,而引入模組的檔案會 之後執行。
這是 CommonJS
中 require()
和 import
之間的區別!使用 require()
,您可以在執行程式時根據需要戴入依賴的項目。如果我們使用 require
而不是 import
來執行此題, 結果將會依 running index.js
,running sum.js
,3
的順序輸出。
console.log(Number(2) === Number(2));
console.log(Boolean(false) === Boolean(false));
console.log(Symbol('foo') === Symbol('foo'));
- A:
true
,true
,false
- B:
false
,true
,false
- C:
true
,false
,true
- D:
true
,true
,true
答案
每個 Symbol 都是完全唯一的。傳遞給 Symbol 的參數只是給 Symbol 的一個描述。 Symbol 的值不依賴於傳遞的參數。當我們建立兩個全新的 Symbol 去比較時:第一個Symbol('foo')
,第二個Symbol('foo')
, 因這兩個值是唯一的,彼此不相等,因此 Symbol('foo') === Symbol('foo')
會得到 false
。
const name = 'Lydia Hallie';
console.log(name.padStart(13));
console.log(name.padStart(2));
- A:
"Lydia Hallie"
,"Lydia Hallie"
- B:
" Lydia Hallie"
," Lydia Hallie"
("[13x whitespace]Lydia Hallie"
,"[2x whitespace]Lydia Hallie"
) - C:
" Lydia Hallie"
,"Lydia Hallie"
("[1x whitespace]Lydia Hallie"
,"Lydia Hallie"
) - D:
"Lydia Hallie"
,"Lyd"
,
答案
使用 padStart
函數,我們可以在字串的前面加上填充字串。傳遞給此函數的參數是字串的總長度(包含填充字串)。字串 Lydia Hallie 的長度為 12
, 因此 name.padStart(13)
在字串的開頭只會插入1個空格,因為 12 + 1 等於 13。
如果傳給 padStart
函數的參數小於字串的長度,則不會加上填充字串。
console.log('🥑' + '💻');
- A:
"🥑💻"
- B:
257548
- C: 一個包含碼位(code point)的字串
- D: 錯誤
function* startGame() {
const answer = yield '你喜歡 JavaScript 嗎?';
if (answer !== 'Yes') {
return "哦,我想我們該走了";
}
return 'JavaScript 也愛你 ❤️';
}
const game = startGame();
console.log(/* 1 */); // 你喜歡 JavaScript 嗎?
console.log(/* 2 */); // JavaScript 也愛你 ❤️
- A:
game.next("Yes").value
andgame.next().value
- B:
game.next.value("Yes")
andgame.next.value()
- C:
game.next().value
andgame.next("Yes").value
- D:
game.next.value()
andgame.next.value("Yes")
答案
generator
函數在遇到 yield 關鍵字時會 “暫停” 執行。首先,我們需要讓函數產生字串 "你喜歡 JavaScript 嗎?",這可以透過呼叫 game.next().value
來完成。
startGame()
函數會一行一行執行直到遇到 yield
關鍵字,在函數裡第一個就有一個 yield
關鍵字:所以執行到第一行就停止了! 此時answer變數還尚未定義
當我們呼叫 game.next("Yes").value
,前一個 yield
被傳遞給 next()
的參數值所取代。此例我們使用 Yes
。變數 answer
的值現在等於 Yes
。 if 語句的條件回傳 false
,並且會回傳 JavaScript 也愛你 ❤️
。
console.log(String.raw`Hello\nworld`);
- A:
Hello world!
- B:
Hello
world
- C:
Hello\nworld
- D:
Hello\n
world
答案
String.raw
會回傳一個字串,其中轉義符(/n
, /v
, /t
等)被忽略! 反斜線可能是一個問題,因為你可能會有這樣的結果。
const path = "C:\Documents\Projects\table.html"
。
將會得到:
C:DocumentsProjects able.html
如果使用String.raw
,它將直接忽略轉譯並輸出。
C:\Documents\Projects\table.html
。
在這種情況下,字串會以 "Hello\nworld",被記錄下來。
async function getData() {
return await Promise.resolve('I made it!');
}
const data = getData();
console.log(data);
- A:
"I made it!"
- B:
Promise {<resolved>: "I made it!"}
- C:
Promise {<pending>}
- D:
undefined
答案
一個異步函數總是回傳一個 promise 。 await
仍然要等待 promise 的 resolve:當我們呼叫 getData()
等於 data
時,會得到一個等待的 promise。
如果我們想獲取 resolve 後的值"I made it"
,我們可以在data
上使用.then()
函數:
data.then(res => console.log(res))
。
這樣就會出現 "I made it!"
的記錄。
function addToList(item, list) {
return list.push(item);
}
const result = addToList('apple', ['banana']);
console.log(result);
- A:
['apple', 'banana']
- B:
2
- C:
true
- D:
undefined
答案
.push()
函數回傳的是陣列的長度!原本陣列包含一個元素(字串"香蕉"
),長度為1
。後來將字串 "apple"
加到陣列中後,陣列包含兩個元素。所以會從addToList
函數中得到,長度為 "2"
。
push
函數修改了原來的陣列。如果你想從函數中回傳 陣列 而不是 陳列的長度 ,你應該在加完item
到陣列後,回傳list
。
const box = { x: 10, y: 20 };
Object.freeze(box);
const shape = box;
shape.x = 100;
console.log(shape);
- A:
{ x: 100, y: 20 }
- B:
{ x: 10, y: 20 }
- C:
{ x: 100 }
- D:
ReferenceError
答案
Object.freeze
使我們無法增加、刪除或修改Object的屬性(除非該屬性的值是另一個Object)。
當我們建立變數shape
並等同被凍結的Objectbox
時,shape
也是指一個被凍結的Object。你可以透過使用Object.isFrozen
檢查一個Object是否被凍結。在這種情況下,Object.isFrozen(shape)
回傳true,因為變數shape
也指向一個凍結Object。
由於shape
是被凍結的,而且x
的值不是一個Object,所以我們不能修改x
的屬性。 x
仍然等於10
,於是{ x: 10, y: 20 }
被記錄下來。
const { name: myName } = { name: "Lydia" };
console.log(name);
- A:
"Lydia"
- B:
"myName"
- C:
undefined
- D:
ReferenceError
答案
當我們從右側的物件解構屬性name
時,我們將其值Lydia
分配給名為myName
的變數。
使用{name:myName}
,我們是在告訴JavaScript我們要建立一個名為myName
的新變數,並且其值是右側物件的name
屬性的值。
當我們嘗試輸出name
,一個未定義的變數時,就會引發ReferenceError
。
function sum(a, b) {
return a + b;
}
- A: Yes
- B: No
答案
純函數一種若輸入參數相同,則永遠會得到相同輸出的函數。
在特定情況下,即使輸入相同參數,也不能得到相同的回傳值:
var a = b = {} a[Symbol.toPrimitive] = b[Symbol.toPrimitive] = () => Math.random() sum(a, b) // Uncertain
所以它不是純函數。
const add = () => {
const cache = {};
return num => {
if (num in cache) {
return `From cache! ${cache[num]}`;
} else {
const result = num + 10;
cache[num] = result;
return `Calculated! ${result}`;
}
};
};
const addFunction = add();
console.log(addFunction(10));
console.log(addFunction(10));
console.log(addFunction(5 * 2));
- A:
Calculated! 20
Calculated! 20
Calculated! 20
- B:
Calculated! 20
From cache! 20
Calculated! 20
- C:
Calculated! 20
From cache! 20
From cache! 20
- D:
Calculated! 20
From cache! 20
Error
答案
add
函數是一個記憶函數。通過記憶化,我們可以暫存函數的結果,以加快其執行速度。上述情況,我們建立一個cache
物件,用於存儲先前存過的值。
如果我們使用相同的參數多次呼叫addFunction
函數,它首先檢查暫存中是否已有該值,如果有,則回傳暫存值,節省執行時間。如果沒有,那麼它將計算該值,並存儲在暫存中。
我們用相同的值三次呼叫了addFunction
函數:
在第一次呼叫,num
等於10
時函數的值尚未暫存,if語句num in cache
回傳false
,else塊的代碼被執行:Calculated! 20
,並且其結果被添加到暫存物件,cache
現在看起來像{10:20}
。
第二次,cache
物件包含10
的回傳值。 if語句 num in cache
回傳true
,印出From cache! 20
。
第三次,我們將5 * 2
(值為10)傳遞給函數。 cache
物件包含10
的回傳值。 if語句 num in cache
回傳true
,印出From cache! 20
。
const myLifeSummedUp = ["☕", "💻", "🍷", "🍫"]
for (let item in myLifeSummedUp) {
console.log(item)
}
for (let item of myLifeSummedUp) {
console.log(item)
}
- A:
0
1
2
3
and"☕"
"💻"
"🍷"
"🍫"
- B:
"☕"
"💻"
"🍷"
"🍫"
and"☕"
"💻"
"🍷"
"🍫"
- C:
"☕"
"💻"
"🍷"
"🍫"
and0
1
2
3
- D:
0
1
2
3
and{0: "☕", 1: "💻", 2: "🍷", 3: "🍫"}
答案
透過for-in
迴圈,我們可以遍歷一個物件自有的、繼承的、可列舉的、非Symbol的屬性。在陣列中,可列舉屬性是陣列元素的“鍵”, 即它們的索引。類似於下面這個物件:
{0: "☕", 1: "💻", 2: "🍷", 3: "🍫"}
其中鍵則是可列舉屬性,因此 0
,1
,2
,3
被記錄。
透過for-of
迴圈,我們可以迭代可迭代物件(包括 Array
,Map
,Set
,String
,arguments
等)。當我們迭代陣列時,在每次迭代中,不同屬性的值將被分配給變數item
, 因此輸出“☕”
,“💻”
,“🍷”
,“🍫”
。
const list = [1 + 2, 1 * 2, 1 / 2]
console.log(list)
- A:
["1 + 2", "1 * 2", "1 / 2"]
- B:
["12", 2, 0.5]
- C:
[3, 2, 0.5]
- D:
[1, 1, 1]
答案
陣列元素可以包含任何值。數字,字符,布林,物件,陣列,null
,undeifned
, 以及其他表達式,如日期,函數和計算式。
元素將等於回傳的值。 1 + 2
回傳3
,1 * 2
回傳'2,'1 / 2
回傳0.5
。
function sayHi(name) {
return `Hi there, ${name}`
}
console.log(sayHi())
- A:
Hi there,
- B:
Hi there, undefined
- C:
Hi there, null
- D:
ReferenceError
答案
預設情況下,如果不傳參數給函數,函數內參數的值將為undefined
。上述情況,我們沒有給參數name
傳值。 name
等於undefined
,並被印出。
在ES6中,我們可以使用預設參數覆蓋此預設的undefined
值。例如:
function sayHi(name =“Lydia”){...}
在這種情況下,如果我們沒有傳遞值或者如果我們傳遞undefined
,name
總是等於字符Lydia
var status = "😎"
setTimeout(() => {
const status = "😍"
const data = {
status: "🥑",
getStatus() {
return this.status
}
}
console.log(data.getStatus())
console.log(data.getStatus.call(this))
}, 0)
- A:
"🥑"
and"😍"
- B:
"🥑"
and"😎"
- C:
"😍"
and"😎"
- D:
"😎"
and"😎"
答案
this
關鍵字的指向取決於使用它的位置。在函數中,比如getStatus
,this
指向的是呼叫它的物件,上述例子中data
物件呼叫了getStatus
,因此this
指向的就是data
物件。當我們輸出this.status
時,data
物件的status
屬性被輸出,即"🥑"
。
使用call
方法,可以更改this
指向的物件。 data.getStatus.call(this)
是將this
的指向由data
物件更改為全局物件。在全局物件上,有一個名為status
的變數,其值為”😎“
。因此輸出this.status
時,會輸出“😎”
。
const person = {
name: "Lydia",
age: 21
}
let city = person.city
city = "Amsterdam"
console.log(person)
- A:
{ name: "Lydia", age: 21 }
- B:
{ name: "Lydia", age: 21, city: "Amsterdam" }
- C:
{ name: "Lydia", age: 21, city: undefined }
- D:
"Amsterdam"
答案
我們將變數city
設置為等於person
物件上名為city
的屬性的值。這個物件上沒有名為city
的屬性,因此變數city
的值為undefined
。
請注意,我們沒有引用person
物件本身,只是將變數city
設置為等於person
物件上city
屬性的當前值。
然後,我們將city
設置為等於字串“Amsterdam”
。這不會更改person物件:沒有對該物件的引用。
因此輸出person
物件時,會回傳未修改的物件。
function checkAge(age) {
if (age < 18) {
const message = "Sorry, you're too young."
} else {
const message = "Yay! You're old enough!"
}
return message
}
console.log(checkAge(21))
- A:
"Sorry, you're too young."
- B:
"Yay! You're old enough!"
- C:
ReferenceError
- D:
undefined
答案
const
和let
定義的變數是具有區塊作用域的,區塊是大括號({}
)之間的任何東西, 即上述情況if / else
語句的大括號。由於區塊作用域,我們無法在定義的塊之外引用變數,因此拋出ReferenceError
。
fetch('https://www.website.com/api/user/1')
.then(res => res.json())
.then(res => console.log(res))
- A:
fetch
函數的結果 - B: 第二次呼叫
fetch
函數的結果 - C: 前一個
.then()
中回傳函數回傳的結果 - D: 總是
undefined
function getName(name) {
const hasName = //
}
- A:
!!name
- B:
name
- C:
new Boolean(name)
- D:
name.length
答案
使用邏輯非運算子!
,將回傳一個布林值,使用!! name
,我們可以確定name
的值是true
還是false
。如果name
是true
,那麼!name
回傳false
。 !false
回傳true
。
通過將hasName
設置為name
,可以將hasName
設置為等於傳遞給getName
函數的值,而不是布林值true
。
new Boolean(true)
回傳一個物件包裝器,而不是布林值本身。
name.length
回傳傳遞的參數的長度,而不是布林值true
。
console.log("I want pizza"[0])
- A:
"""
- B:
"I"
- C:
SyntaxError
- D:
undefined
答案
可以使用雙括號表示法獲取字串中特定索引的字串,字串中的第一個字串具有索引0,依此類推。在這種情況下,我們想要得到索引為0的元素,字串'I'
被記錄。
請注意,IE7及更低版本不支援此方法。應該使用.charAt()
function sum(num1, num2 = num1) {
console.log(num1 + num2)
}
sum(10)
- A:
NaN
- B:
20
- C:
ReferenceError
- D:
undefined
答案
您可以將預設參數的值設置為函數的另一個參數,只要另一個參數定義在其之前即可。我們將值10
傳遞給sum
函數。如果sum
函數只接收1個參數,則意味著沒有傳遞num2
的值,這種情況下,num1
的值等於傳遞的值10
。 num2
的預設值是num1
的值,即10
。 num1 + num2
回傳20
。
如果您嘗試將預設參數的值設置為後面定義的參數,則可能導致參數的值尚未初始化,從而引發錯誤。比如:
function test(m = n, n = 2) {
console.log(m, n)
}
test() // Uncaught ReferenceError: Cannot access 'n' before initialization
test(3) // 3 2
test(3, 4) // 3 4
// module.js
export default () => "Hello world"
export const name = "Lydia"
// index.js
import * as data from "./module"
console.log(data)
- A:
{ default: function default(), name: "Lydia" }
- B:
{ default: function default() }
- C:
{ default: "Hello world", name: "Lydia" }
- D: Global object of
module.js
答案
使用import * as name
語法,我們將module.js
文件中所有export
匯入到index.js
文件中,並且建立了一個名為data
的新物件。在module.js
文件中,有兩個匯出:預設匯出和命名匯出。預設匯出是一個回傳字串“Hello World”的函數,命名匯出是一個名為name
的變數,其值為字串“Lydia”
。
data
物件具有預設匯出的default
屬性,其他屬性具有指定exports的名稱及其對應的值。
class Person {
constructor(name) {
this.name = name
}
}
const member = new Person("John")
console.log(typeof member)
- A:
"class"
- B:
"function"
- C:
"object"
- D:
"string"
答案
class是建構函數的語法糖,如果用建構函數的方式來重寫Person
class則會是:
function Person() {
this.name = name
}
透過new
來呼叫建構函數,將會產生建構函數Person
的實例,對實例執行typeof
關鍵字將回傳"object"
,上述情況輸出"object"
。
let newList = [1, 2, 3].push(4)
console.log(newList.push(5))
- A:
[1, 2, 3, 4, 5]
- B:
[1, 2, 3, 5]
- C:
[1, 2, 3, 4]
- D:
Error
答案
.push
函數回傳陣列的長度,而不是陣列本身!通過將newList
設置為[1,2,3].push(4)
,實際上newList
等於陣列的新長度:4
。
然後,嘗試在newList
上使用.push
函數。由於newList
是數值4
,拋出TypeError。
function giveLydiaPizza() {
return "Here is pizza!"
}
const giveLydiaChocolate = () => "Here's chocolate... now go hit the gym already."
console.log(giveLydiaPizza.prototype)
console.log(giveLydiaChocolate.prototype)
- A:
{ constructor: ...}
{ constructor: ...}
- B:
{}
{ constructor: ...}
- C:
{ constructor: ...}
{}
- D:
{ constructor: ...}
undefined
答案
正規式函數,例如giveLydiaPizza
函數,有一個prototype
屬性,它是一個帶有constructor
屬性的物件(原型物件)。然而,箭頭函數,例如giveLydiaChocolate
函數,沒有這個prototype
屬性。嘗試使用giveLydiaChocolate.prototype
存取prototype
屬性時會得到undefined
。
const person = {
name: "Lydia",
age: 21
}
for (const [x, y] of Object.entries(person)) {
console.log(x, y)
}
- A:
name
Lydia
andage
21
- B:
["name", "Lydia"]
and["age", 21]
- C:
["name", "age"]
andundefined
- D:
Error
答案
Object.entries()
函數回傳一個給定物件本身可枚舉屬性的鍵值對陣列,上述情況回傳一個二維陣列,陣列每個元素是一個包含鍵和值的陣列:
[['name','Lydia'],['age',21]]
使用for-of
循環,我們可以迭代陣列中的每個元素,上述情況是子陣列。我們可以使用const [x,y]
在for-of
循環中解構子陣列。 x
等於子陣列中的第一個元素,y
等於子陣列中的第二個元素。
第一個子陣列是[“name”,“Lydia”]
,其中x
等於name
,而y
等於Lydia
。
第二個子陣列是[“age”,21]
,其中x
等於age
,而y
等於21
。
function getItems(fruitList, ...args, favoriteFruit) {
return [...fruitList, ...args, favoriteFruit]
}
getItems(["banana", "apple"], "pear", "orange")
- A:
["banana", "apple", "pear", "orange"]
- B:
[["banana", "apple"], "pear", "orange"]
- C:
["banana", "apple", ["pear"], "orange"]
- D:
SyntaxError
答案
... args
是剩餘參數,剩餘參數的值是一個包含所有剩餘參數的陣列,並且只能作為最後一個參數。上面示範中,剩餘參數是第二個參數,這是不可能的,並會拋出語法錯誤。
function getItems(fruitList, favoriteFruit, ...args) {
return [...fruitList, ...args, favoriteFruit]
}
getItems(["banana", "apple"], "pear", "orange")
上面示範中是有效的,將會回傳陣列:[ 'banana', 'apple', 'orange', 'pear' ]
function nums(a, b) {
if
(a > b)
console.log('a is bigger')
else
console.log('b is bigger')
return
a + b
}
console.log(nums(4, 2))
console.log(nums(1, 2))
- A:
a is bigger
,6
andb is bigger
,3
- B:
a is bigger
,undefined
andb is bigger
,undefined
- C:
undefined
andundefined
- D:
SyntaxError
答案
在JavaScript中,我們不必硬性寫分號(;
),但是JavaScript引擎仍然在語法之後自動補上分號。這稱為自動分號插入。例如,一個語法可以是變數,或者像throw
、return
、break
這樣的關鍵字。
在這裡,我們在新的一行上寫了一個return
語法和另一個值a + b
。然而,由於它是一個新的一行,引擎並不知道它實際上是我們想要回傳的值。相反,它會在return
後面自動補上分號。你可以這樣看:
return;
a + b
這意味著永遠不會到達a + b
,因為函數在return
關鍵字之後停止執行。如果沒有回傳值,就像這裡,函數回傳undefined
。注意,在if/else
語法之後沒有自動插入!
class Person {
constructor() {
this.name = "Lydia"
}
}
Person = class AnotherPerson {
constructor() {
this.name = "Sarah"
}
}
const member = new Person()
console.log(member.name)
- A:
"Lydia"
- B:
"Sarah"
- C:
Error: cannot redeclare Person
- D:
SyntaxError
答案
我們可以將class設置為等於其他class/函數建構函數。在這種情況下,我們將Person
設置為AnotherPerson
。這個建構函數的名字是Sarah
,所以新的Person
實例member
上的name屬性是Sarah
。
const info = {
[Symbol('a')]: 'b'
}
console.log(info)
console.log(Object.keys(info))
- A:
{Symbol('a'): 'b'}
and["{Symbol('a')"]
- B:
{}
and[]
- C:
{ a: "b" }
and["a"]
- D:
{Symbol('a'): 'b'}
and[]
答案
Symbol
類型是不可枚舉的。 Object.keys
函數回傳物件上的所有可枚舉的鍵屬性。 Symbol
類型是不可見的,並回傳一個空陣列。記錄整個物件時,所有屬性都是可見的,甚至是不可枚舉的屬性。
這是Symbol
的眾多特性之一:除了表示完全唯一的值(防止物件意外名稱衝突,例如當使用2個想要向同一物件添加屬性的庫時),您還可以隱藏
這種方式物件的屬性(儘管不完全。你仍然可以使用Object.getOwnPropertySymbols()
函數存取Symbol
。
const getList = ([x, ...y]) => [x, y]
const getUser = user => { name: user.name, age: user.age }
const list = [1, 2, 3, 4]
const user = { name: "Lydia", age: 21 }
console.log(getList(list))
console.log(getUser(user))
- A:
[1, [2, 3, 4]]
andundefined
- B:
[1, [2, 3, 4]]
and{ name: "Lydia", age: 21 }
- C:
[1, 2, 3, 4]
and{ name: "Lydia", age: 21 }
- D:
Error
and{ name: "Lydia", age: 21 }
答案
getList
函數接收一個陣列作為其參數。在getList
函數的括號之間,我們立即解構這個陣列。您可以這樣表達:
[x, ...y] = [1, 2, 3, 4]
使用剩餘的參數... y
,我們將所有剩餘參數放在一個陣列中。在這種情況下,其餘的參數是2
,3
和4
。 y
的值是一個陣列,包含所有其餘參數。在這種情況下,x
的值等於1
,所以當我們輸出[x,y]
時,會輸出[1,[2,3,4]]
。
getUser
函數接收一個物件。對於箭頭函數,如果只回傳一個值,我們不必編寫大括號。但是,如果您想從一個箭頭函數回傳一個物件,您必須在小括號之間編寫它,否則不會回傳任何值!下面的函數將回傳一個物件:
const getUser = user => ({ name: user.name, age: user.age })
由於在這種情況下不回傳任何值,因此該函數回傳undefined
。
const name = "Lydia"
console.log(name())
- A:
SyntaxError
- B:
ReferenceError
- C:
TypeError
- D:
undefined
答案
變數name
保存字串的值,該字串不是函數,因此無法呼叫。
當值不是預期類型時,會拋出TypeErrors
。 JavaScript期望name
是一個函數,因為我們試圖呼叫它。但它是一個字串,因此拋出TypeError
:name is not a function
當你編寫了一些非有效的JavaScript時,會拋出語法錯誤,例如當你把return
這個詞寫成retrun
時。
當JavaScript無法找到您嘗試存取的值的引用時,拋出ReferenceErrors
。
// 🎉✨ 耶! 我終於翻到100題了! 噢耶! ✨🎉
const output = `${[] && 'Im'}possible!
You should${'' && `n't`} see a therapist after so much JavaScript lol`
- A:
possible! You should see a therapist after so much JavaScript lol
- B:
Impossible! You should see a therapist after so much JavaScript lol
- C:
possible! You shouldn't see a therapist after so much JavaScript lol
- D:
Impossible! You shouldn't see a therapist after so much JavaScript lol
答案
[]
是一個真值。使用&&
運算子,如果左側值是真值,則回傳右側值。在這種情況下,左側值[]
是一個真值,所以回傳Im
。
""
是一個假值。如果左側值是假的,則不回傳任何內容。 n't
不會被退回。
const one = (false || {} || null)
const two = (null || false || "")
const three = ([] || 0 || true)
console.log(one, two, three)
- A:
false
null
[]
- B:
null
""
true
- C:
{}
""
[]
- D:
null
null
true
答案
使用||
運算子,我們可以得到第一個真值。如果所有值都是假值,則得到最後一個值。
(false || {} || null)
:空物件{}
是一個真值。這是第一個(也是唯一的)真值,它將被得到。 one
等於{}
。
(null || false ||“”)
:所有值都是假值。這意味著得到傳遞的值""
。 two
等於""
。
([] || 0 ||“”)
:空陣列[]
是一個真值。這是第一個得到的真值。 three
等於[]
。
const myPromise = () => Promise.resolve('I have resolved!')
function firstFunction() {
myPromise().then(res => console.log(res))
console.log('second')
}
async function secondFunction() {
console.log(await myPromise())
console.log('second')
}
firstFunction()
secondFunction()
- A:
I have resolved!
,second
andI have resolved!
,second
- B:
second
,I have resolved!
andsecond
,I have resolved!
- C:
I have resolved!
,second
andsecond
,I have resolved!
- D:
second
,I have resolved!
andI have resolved!
,second
答案
有了promise,我們通常會說:當我想要呼叫某個函數,但是由於它可能需要一段時間,因此暫時將它放在一邊。只有當某個值被resolved/rejected,並且執行序為空時才使用這個值。
我們可以在async
函數中通過.then
和await
關鍵字獲得該值。儘管我們可以通過.then
和await
獲得promise的值,但是它們的運作方式不同。
在firstFunction
中,當執行到myPromise
函數時我們將其放在一邊,即promise進入微任務佇列,其他後面的程式(console.log('second')
)照常執行,因此second
被輸出,firstFunction
函數到此執行完畢,執行序中任務佇列被清空,此時開始執行微任務佇列中的任務,I have resolved
被輸出。
在secondFunction
函數中,我們通過await
關鍵字,暫停了後面程式的執行,直到異步函數的值被解析才開始後面程式的執行。這意味著,它會等著直到 myPromise
以值I have resolved
被resolve
之後,下一行second
才開始執行。
const set = new Set()
set.add(1)
set.add("Lydia")
set.add({ name: "Lydia" })
for (let item of set) {
console.log(item + 2)
}
- A:
3
,NaN
,NaN
- B:
3
,7
,NaN
- C:
3
,Lydia2
,[Object object]2
- D:
"12"
,Lydia2
,[Object object]2
答案
“+”運算子不僅用於相加數字,還可以使用它來連接字串。每當JavaScript引擎發現一個或多個值不是數字時,就會將數字強制為字串。
第一個是數字1。 1 + 2得到數字3。
但是,第二個是字串“Lydia”。 “Lydia”是一個字串,2是一個數字:2被強制轉換為字串。 “Lydia”和“2”被連接起來,產生字串“Lydia2”。
{name:“ Lydia”}
是一個物件。數字和物件都不是字串,因此將二者都字串化。每當我們對正規式物件進行字串化時,它就會變成[Object object]
。與“2”串聯的“ [Object object]”成為“[Object object]2”。
Promise.resolve(5)
- A:
5
- B:
Promise {<pending>: 5}
- C:
Promise {<fulfilled>: 5}
- D:
Error
答案
我們可以將我們想要的任何類型的值傳遞Promise.resolve
,無論是否promise
。該函數本身回傳帶有已解析值的Promise
(<fulfilled>
)。如果您傳遞正規式函數,它將是具有正規式值的已解決promise
。如果你通過了promise,它將是一個已經resolved的且帶有傳的值的promise。
上述情況,我們傳了數字5,因此回傳一個resolved狀態的promise,resolve值為5
function compareMembers(person1, person2 = person) {
if (person1 !== person2) {
console.log("Not the same!")
} else {
console.log("They are the same!")
}
}
const person = { name: "Lydia" }
compareMembers(person)
- A:
Not the same!
- B:
They are the same!
- C:
ReferenceError
- D:
SyntaxError
答案
物件通過參考位址傳遞。當我們檢查物件的嚴格相等性(===)時,我們正在比較它們的參考位址。
我們將“person2”的預設值設置為“person”物件,並將“person”物件作為“person1”的值傳遞。
這意味著兩個值都引用緩存中的同一位置,因此它們是相等的。
執行“ else”語句中的代碼塊,並記錄They are the same!
。
const colorConfig = {
red: true,
blue: false,
green: true,
black: true,
yellow: false,
}
const colors = ["pink", "red", "blue"]
console.log(colorConfig.colors[1])
- A:
true
- B:
false
- C:
undefined
- D:
TypeError
答案
在JavaScript中,我們有兩種存取物件屬性的方法:括號表示法或點表示法。在此範例中,我們使用點表示法(colorConfig.colors
)代替括號表示法(colorConfig [“ colors”]
)。
使用點表示法,JavaScript會嘗試使用該確切名稱在物件上查找屬性。在此範例中,JavaScript嘗試在colorconfig物件上找到名為colors的屬性。沒有名為“colors”的屬性,因此得到“undefined”。
然後,我們嘗試使用[1]
存取第一個元素的值。我們無法對未定義的值執行此操作,因此會拋出Cannot read property '1' of undefined
。
JavaScript解釋(或取消裝箱)語句。當我們使用中括號表示法時,它會看到第一個左方括號[
並一直進行下去,直到找到右方括號]
。只有這樣,它才會評估該語句。如果我們使用了colorConfig [colors [1]],它將得到colorConfig物件上red屬性的值。
console.log('❤️' === '❤️')
- A:
true
- B:
false
答案
在內部,表情符號是unicode。 heat表情符號的unicode是“ U + 2764 U + FE0F”
。對於相同的表情符號,它們總是相同的,因此我們將兩個相等的字串相互比較,這將回傳true。
const emojis = ['✨', '🥑', '😍']
emojis.map(x => x + '✨')
emojis.filter(x => x !== '🥑')
emojis.find(x => x !== '🥑')
emojis.reduce((acc, cur) => acc + '✨')
emojis.slice(1, 2, '✨')
emojis.splice(1, 2, '✨')
- A:
All of them
- B:
map
reduce
slice
splice
- C:
map
slice
splice
- D:
splice
答案
使用splice
方法,我們透過刪除,取代或增加元素來修改原始陣列。在這種情況下,我們從索引1中刪除了2個元素(我們刪除了'🥑'
和'😍'
),同時增加了✨emoji表情。
map
,filter
和slice
回傳一個新陣列,find
回傳一個元素,而reduce
回傳一個計算過的值。
const food = ['🍕', '🍫', '🥑', '🍔']
const info = { favoriteFood: food[0] }
info.favoriteFood = '🍝'
console.log(food)
- A:
['🍕', '🍫', '🥑', '🍔']
- B:
['🍝', '🍫', '🥑', '🍔']
- C:
['🍝', '🍕', '🍫', '🥑', '🍔']
- D:
ReferenceError
答案
我們將info
物件上的favoriteFood
屬性的值設置為披薩表情符號“🍕”的字串。字串是原始內容類型。在JavaScript中,原始內容類型通過值起作用
在這種情況下,我們將info
物件上的favoriteFood
屬性的值設置為等於food
陣列中的第一個元素的值,字串為披薩表情符號('🍕'
)。字串是原始內容類型,並且通過值進行交換,我們更改info
物件上favoriteFood
屬性的值。 food陣列沒有改變,因為favoriteFood的值只是該陣列中第一個元素的值的複製,並且與該元素上的元素沒有相同的緩存引用食物[0]
。當我們記錄食物時,它仍然是原始陣列['🍕','🍫','🥑','🍔']
。
JSON.parse()
- A: Parses JSON to a JavaScript value
- B: Parses a JavaScript object to JSON
- C: Parses any JavaScript value to JSON
- D: Parses JSON to a JavaScript object only
答案
使用JSON.parse()
函數,我們可以將JSON字串解析為JavaScript值。
// 將數字字串化為有效的JSON,然後將JSON字串解析為JavaScript值:
const jsonNumber = JSON.stringify(4) // '4'
JSON.parse(jsonNumber) // 4
// 將陣列值字串化為有效的JSON,然後將JSON字串解析為JavaScript值:
const jsonArray = JSON.stringify([1, 2, 3]) // '[1, 2, 3]'
JSON.parse(jsonArray) // [1, 2, 3]
// 將物件字串化為有效的JSON,然後將JSON字串解析為JavaScript值:
const jsonArray = JSON.stringify({ name: "Lydia" }) // '{"name":"Lydia"}'
JSON.parse(jsonArray) // { name: 'Lydia' }
let name = 'Lydia'
function getName() {
console.log(name)
let name = 'Sarah'
}
getName()
- A: Lydia
- B: Sarah
- C:
undefined
- D:
ReferenceError
答案
每個函數都有其自己的執行上下文。 getName
函數首先在其自身的上下文(範圍)內查找,以查看其是否包含我們嘗試存取的變數name
。上述情況,getName
函數包含其自己的name
變數:我們用let
關鍵字和Sarah
的值定義變數name
。
帶有let
關鍵字(和const
)的變數被提升,但是與var
不同,它不會被初始化。在我們定義(初始化)它們之前,無法存取它們。這稱為“暫時性死區”。當我們嘗試在定義變數之前存取變數時,JavaScript會拋出ReferenceError: Cannot access 'name' before initialization
。
如果我們不在getName
函數中定義name
變數,則javascript引擎會查看原型鏈。會找到其外部作用域有一個名為name
的變數,其值為Lydia
。在這種情況下,它將輸出Lydia
:
let name = 'Lydia'
function getName() {
console.log(name)
}
getName() // Lydia
function* generatorOne() {
yield ['a', 'b', 'c'];
}
function* generatorTwo() {
yield* ['a', 'b', 'c'];
}
const one = generatorOne()
const two = generatorTwo()
console.log(one.next().value)
console.log(two.next().value)
- A:
a
anda
- B:
a
andundefined
- C:
['a', 'b', 'c']
anda
- D:
a
and['a', 'b', 'c']
答案
透過yield
關鍵字, 我們在Generator
函數裡執行yield
語法. 透過yield*
關鍵字, 我們可以在一個Generator
函數裡面執行(yield
語法)另一個Generator
函數, 或可遍歷的物件(如陣列).
在函數 generatorOne
中, 我們透過 yield
關鍵字 yield 了一個完整的陣列 ['a', 'b', 'c']
。函數one
透過next
方法回傳的物件的value
屬性的值 (one.next().value
) 等價於陣列 ['a', 'b', 'c']
.
console.log(one.next().value) // ['a', 'b', 'c']
console.log(one.next().value) // undefined
在函數 generatorTwo
中, 我們使用 yield*
關鍵字。就相當於函數two
第一個yield
的值, 等價於在迭代器中第一個 yield
的值。陣列['a', 'b', 'c']
就是這個迭代器. 第一個yield
的值就是a
, 所以我們第一次呼叫two.next().value
時, 就回傳a
。
console.log(two.next().value) // 'a'
console.log(two.next().value) // 'b'
console.log(two.next().value) // 'c'
console.log(two.next().value) // undefined
console.log(`${(x => x)('I love')} to program`)
- A:
I love to program
- B:
undefined to program
- C:
${(x => x)('I love') to program
- D:
TypeError
答案
帶有模板字面量的表達式優先被執行。相當於字串會包含表達式,這個立即執行函數(x => x)('I love')
回傳的值. 我們向箭頭函數x => x
傳遞'I love'
作為參數。 x
等價於回傳的 'I love'
。這就是結果 I love to program
。
let config = {
alert: setInterval(() => {
console.log('Alert!')
}, 1000)
}
config = null
- A:
setInterval
裡的函數不會被呼叫 - B:
setInterval
裡的函數被呼叫一次 - C:
setInterval
裡的函數仍然會被每秒鐘呼叫 - D: 我們從沒呼叫過
config.alert()
, config 為null
答案
一般情況下當我們將物件賦值為 null
, 那些物件會被進行 垃圾回收(garbage collected) 因為已經沒有對這些物件的引用了。然而,setInterval
的參數是一個箭頭函數(所以上下文綁定到物件 config
了),函數仍然保留著對 config
的引用。只要存在引用,物件就不會被垃圾回收。因為沒有被垃圾回收,setInterval
的每1000ms (1s)會被呼叫一次。
const myMap = new Map()
const myFunc = () => 'greeting'
myMap.set(myFunc, 'Hello world!')
//1
myMap.get('greeting')
//2
myMap.get(myFunc)
//3
myMap.get(() => 'greeting')
- A: 1
- B: 2
- C: 2 and 3
- D: All of them
答案
當透過 set
函數增加一個鍵值對,一個傳遞給 set
函數的參數將會是鍵名,第二個參數將會是值。在這個case裡,鍵名為 函數 () => 'greeting'
,值為'Hello world'
。 myMap
現在就是 { () => 'greeting' => 'Hello world!' }
。
1 是錯的,因為鍵名不是 'greeting'
而是 () => 'greeting'
。
3 是錯的,因為我們給get
函數傳遞了一個新的函數。物件受 引用 影響。函數也是物件,因此兩個函數嚴格上並不等價,儘管他們相同:他們有兩個不同的緩存引用地址。
const person = {
name: "Lydia",
age: 21
}
const changeAge = (x = { ...person }) => x.age += 1
const changeAgeAndName = (x = { ...person }) => {
x.age += 1
x.name = "Sarah"
}
changeAge(person)
changeAgeAndName()
console.log(person)
- A:
{name: "Sarah", age: 22}
- B:
{name: "Sarah", age: 23}
- C:
{name: "Lydia", age: 22}
- D:
{name: "Lydia", age: 23}
答案
函數 changeAge
和函數 changeAgeAndName
有著不同的參數,定義一個 新 生成的物件 { ...person }
。這個物件有著所有 person
物件 中 k/v 值的副本。
首項, 我們呼叫 changeAge
函數並傳遞 person
物件作為它的參數。這個函數對 age
屬性進行加一操作。 person
現在是 { name: "Lydia", age: 22 }
。
然後,我們呼叫函數 changeAgeAndName
,然而我們沒有傳遞參數。取而代之,x
的值等價 new 生成的物件: { ...person }
。因為它是一個新生成的物件,它並不會對物件 person
造成任何副作用。 person
仍然等價於 { name: "Lydia", age: 22 }
。
function sumValues(x, y, z) {
return x + y + z;
}
- A:
sumValues([...1, 2, 3])
- B:
sumValues([...[1, 2, 3]])
- C:
sumValues(...[1, 2, 3])
- D:
sumValues([1, 2, 3])
答案
通過展開語法 ...
,我們可以 展開 單個可迭代的元素。函數 sumValues
function 接收三個參數: x
, y
和 z
。 ...[1, 2, 3]
的執行結果為 1, 2, 3
,將會傳遞給函數 sumValues
。
let num = 1;
const list = ["🥳", "🤠", "🥰", "🤪"];
console.log(list[(num += 1)]);
- A:
🤠
- B:
🥰
- C:
SyntaxError
- D:
ReferenceError
答案
透過 +=
運算子,我們對變數 num
進行加 1
操作。 num
有初始值 1
,因此 1 + 1
的執行結果為 2
。陣列 list
的第二項為 🥰,console.log(list[2])
輸出 🥰.
const person = {
firstName: "Lydia",
lastName: "Hallie",
pet: {
name: "Mara",
breed: "Dutch Tulip Hound"
},
getFullName() {
return `${this.firstName} ${this.lastName}`;
}
};
console.log(person.pet?.name);
console.log(person.pet?.family?.name);
console.log(person.getFullName?.());
console.log(member.getLastName?.());
- A:
undefined
undefined
undefined
undefined
- B:
Mara
undefined
Lydia Hallie
undefined
- C:
Mara
null
Lydia Hallie
null
- D:
null
ReferenceError
null
ReferenceError
答案
通過ES10 或TS3.7+[可選鏈運算子?.
](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/%E5%8F%AF% E9%80%89%E9%93%BE),我們不再需要顯式檢測更深層的嵌套值是否有效。如果我們嘗試存取 undefined
或 null
的值 (nullish),表達將會短路並回傳 undefined
.
person.pet?.name
: person
有一個名為 pet
的屬性: person.pet
不是 nullish。它有個名為 name
的屬性,並回傳字串 Mara
。
person.pet?.family?.name
: person
有一個名為pet
的屬性: person.pet
不是nullish. pet
並沒有 一個名為family
的屬性, person.pet.family
是nullish。表達式回傳 undefined
。
person.getFullName?.()
: person
有一個名為 getFullName
的屬性: person.getFullName()
不是 nullish 並可以被呼叫,回傳字串 Lydia Hallie
。
member.getLastName?.()
: member
is not defined: member.getLastName()
is nullish. The expression returns undefined
.
const groceries = ["banana", "apple", "peanuts"];
if (groceries.indexOf("banana")) {
console.log("We have to buy bananas!");
} else {
console.log(`We don't have to buy bananas!`);
}
- A: We have to buy bananas!
- B: We don't have to buy bananas
- C:
undefined
- D:
1
答案
我們傳遞了一個狀態 groceries.indexOf("banana")
給if語法。 groceries.indexOf("banana")
回傳 0
, 一個 falsy 的值。因為if語法的狀態為 falsy,else
塊區內的代碼執行,並且 We don't have to buy bananas!
被輸出.
const config = {
languages: [],
set language(lang) {
return this.languages.push(lang);
}
};
console.log(config.language);
- A:
function language(lang) { this.languages.push(lang }
- B:
0
- C:
[]
- D:
undefined
const name = "Lydia Hallie";
console.log(!typeof name === "object");
console.log(!typeof name === "string");
- A:
false
true
- B:
true
false
- C:
false
false
- D:
true
true
答案
typeof name
回傳 "string"
。字串 "string"
是一個 truthy 的值,因此 !typeof name
回傳一個布林值 false
。 false === "object"
和 false === "string"
都回傳 false
。
(如果我們想檢測一個值的類型,我們應該用 !==
而不是 !typeof
)
const add = x => y => z => {
console.log(x, y, z);
return x + y + z;
};
add(4)(5)(6);
- A:
4
5
6
- B:
6
5
4
- C:
4
function
function
- D:
undefined
undefined
6
答案
函數 add
是一個回傳 回傳箭頭函數的箭頭函數 的箭頭函數(still with me?)。第一個函數接收一個值為 4
的參數 x
。我們呼叫第二個函數,它接收一個值為 5
的參數 y
。然後我們呼叫第三個函數,它接收一個值為 6
的參數 z
。當我們嘗試在最後一個箭頭函數中獲取 x
, y
和 z
的值,JS 引擎根據作用域鏈去找 x
和 y
的值。得到 4
5
6
.
async function* range(start, end) {
for (let i = start; i <= end; i++) {
yield Promise.resolve(i);
}
}
(async () => {
const gen = range(1, 3);
for await (const item of gen) {
console.log(item);
}
})();
- A:
Promise {1}
Promise {2}
Promise {3}
- B:
Promise {<pending>}
Promise {<pending>}
Promise {<pending>}
- C:
1
2
3
- D:
undefined
undefined
undefined
答案
我們給 函數range 傳遞: Promise{1}
, Promise{2}
, Promise{3}
,Generator 函數 range
回傳一個全是 async object promise 陣列。我們將 async object 賦值給變數 gen
,之後我們使用for await ... of
進行循環遍歷。我們將回傳的 Promise 實例賦值給 item
: 第一個回傳 Promise{1}
, 第二個回傳 Promise{2}
,之後是 Promise{3}
。因為我們正 awaiting item
的值,resolved 狀態的 promsie,promise陣列的resolved 值 以此為: 1
,2
,3
.
const myFunc = ({ x, y, z }) => {
console.log(x, y, z);
};
myFunc(1, 2, 3);
- A:
1
2
3
- B:
{1: 1}
{2: 2}
{3: 3}
- C:
{ 1: undefined }
undefined
undefined
- D:
undefined
undefined
undefined
答案
myFunc
預期接收一個包含 x
, y
和 z
屬性的物件作為它的參數。因為我們僅僅傳遞三個單獨的數字值(1, 2, 3) 而不是一個含有x
, y
和z
屬性的物件({x: 1, y: 2, z: 3}) , x
, y
和z
有著各自的預設值undefined
.
function getFine(speed, amount) {
const formattedSpeed = new Intl.NumberFormat({
'en-US',
{ style: 'unit', unit: 'mile-per-hour' }
}).format(speed)
const formattedAmount = new Intl.NumberFormat({
'en-US',
{ style: 'currency', currency: 'USD' }
}).format(amount)
return `The driver drove ${formattedSpeed} and has to pay ${formattedAmount}`
}
console.log(getFine(130, 300))
- A: The driver drove 130 and has to pay 300
- B: The driver drove 130 mph and has to pay $300.00
- C: The driver drove undefined and has to pay undefined
- D: The driver drove 130.00 and has to pay 300.00
答案
通過函數 Intl.NumberFormat
,我們可以格式化任意區域的數字值。我們對數字值 130
進行 mile-per-hour
作為 unit
的 en-US
區域 格式化,結果為 130 mph
。對數字值 300
進行 USD
作為 currentcy
的 en-US
區域格式化,結果為 $300.00
.
const spookyItems = ["👻", "🎃", "🕸"];
({ item: spookyItems[3] } = { item: "💀" });
console.log(spookyItems);
- A:
["👻", "🎃", "🕸"]
- B:
["👻", "🎃", "🕸", "💀"]
- C:
["👻", "🎃", "🕸", { item: "💀" }]
- D:
["👻", "🎃", "🕸", "[object Object]"]
答案
通過解構物件們,我們可以從右手邊的物件中拆出值,並且將拆出的值分配給左手邊物件同名的屬性。在這種情況下,我們將值 "💀" 分配給 spookyItems[3]
。相當於我們正在篡改陣列 spookyItems
,我們給它添加了值 "💀"。當輸出 spookyItems
時,結果為 ["👻", "🎃", "🕸", "💀"]
。
const name = "Lydia Hallie";
const age = 21;
console.log(Number.isNaN(name));
console.log(Number.isNaN(age));
console.log(isNaN(name));
console.log(isNaN(age));
- A:
true
false
true
false
- B:
true
false
false
false
- C:
false
false
true
false
- D:
false
true
false
true
答案
通過函數 Number.isNaN
,你可以檢測你傳遞的值是否為 數字值 並且是否等於 NaN
。 name
不是一個數字值,因此 Number.isNaN(name)
回傳 false
。 age
是一個數字值,但它不等於 NaN
,因此 Number.isNaN(age)
回傳 false
.
通過函數 isNaN
, 你可以檢測你傳遞的值是否一個 number。 name
不是一個 number
,因此 isNaN(name)
回傳 true
. age
是一個 number
因此 isNaN(age)
回傳 false
.
const randomValue = 21;
function getInfo() {
console.log(typeof randomValue);
const randomValue = "Lydia Hallie";
}
getInfo();
- A:
"number"
- B:
"string"
- C:
undefined
- D:
ReferenceError
答案
通過 const
關鍵字定義的變數在被初始化之前不可被引用:這被稱之為 暫時性死區。在函數 getInfo
中, 變數 randomValue
定義在getInfo
的作用域的此法環境中。在想要對typeof randomValue
進行log之前,變數randomValue
仍未被初始化: 錯誤ReferenceError
被拋出! JS引擎並不會根據作用域鍊網上尋找該變數,因為我們已經在getInfo
函數中定義了randomValue
變數。
const myPromise = Promise.resolve("Woah some cool data");
(async () => {
try {
console.log(await myPromise);
} catch {
throw new Error(`Oops didn't work`);
} finally {
console.log("Oh finally!");
}
})();
- A:
Woah some cool data
- B:
Oh finally!
- C:
Woah some cool data
Oh finally!
- D:
Oops didn't work
Oh finally!
答案
在 try
塊區,我們輸出 myPromise
變數的 awaited 值: "Woah some cool data"
。因為try
塊區沒有錯誤拋出,catch
塊區的代碼並不執行。 finally
塊區的代碼 總是 執行,"Oh finally!"
被輸出。
const emojis = ["🥑", ["✨", "✨", ["🍕", "🍕"]]];
console.log(emojis.flat(1));
- A:
['🥑', ['✨', '✨', ['🍕', '🍕']]]
- B:
['🥑', '✨', '✨', ['🍕', '🍕']]
- C:
['🥑', ['✨', '✨', '🍕', '🍕']]
- D:
['🥑', '✨', '✨', '🍕', '🍕']
答案
通過函數 flat
, 我們可以建立一個新的, 已被扁平化的陣列。被扁平化的深度取決於我們傳遞的值。在這個case裡,我們傳遞了值 1
(並不必要,這是預設值),相當於只有第一層的陣列才會被連接。即這個 case 裡的 ['🥑']
and ['✨', '✨', ['🍕', '🍕']]
。連接這兩個陣列得到結果 ['🥑', '✨', '✨', ['🍕', '🍕']]
.
class Counter {
constructor() {
this.count = 0;
}
increment() {
this.count++;
}
}
const counterOne = new Counter();
counterOne.increment();
counterOne.increment();
const counterTwo = counterOne;
counterTwo.increment();
console.log(counterOne.count);
- A:
0
- B:
1
- C:
2
- D:
3
答案
counterOne
是類 Counter
的一個實例。類 Counter 包含一個count
屬性在它的建構函數里, 和一個 increment
函數。首先,我們通過 counterOne.increment()
呼叫函數 increment
兩次。現在, counterOne.count
為 2
.
然後,我們建立一個新的變數 counterTwo
並將 counterOne
的引用地址賦值給它。因為物件受引用地址的影響,我們剛剛建立了一個新的物件,其引用地址和 counterOne
的等價。因此它們指向同一塊緩存地址,任何對其的副作用都會影響 counterTwo
。現在 counterTwo.count
為 2
。
我們呼叫 counterTwo.increment()
將 count
的值設為 3
。然後,我們輸出 counterOne
裡的count,結果為 3
。
const myPromise = Promise.resolve(Promise.resolve("Promise!"));
function funcOne() {
myPromise.then(res => res).then(res => console.log(res));
setTimeout(() => console.log("Timeout!", 0));
console.log("Last line!");
}
async function funcTwo() {
const res = await myPromise;
console.log(await res);
setTimeout(() => console.log("Timeout!", 0));
console.log("Last line!");
}
funcOne();
funcTwo();
- A:
Promise! Last line! Promise! Last line! Last line! Promise!
- B:
Last line! Timeout! Promise! Last line! Timeout! Promise!
- C:
Promise! Last line! Last line! Promise! Timeout! Timeout!
- D:
Last line! Promise! Promise! Last line! Timeout! Timeout!
答案
首先,我們呼叫 funcOne
。在函數 funcOne
的第一行,我們呼叫myPromise
promise 異步操作。當JS引擎在忙於執行 promise,它繼續執行函數 funcOne
。下一行 異步操作 setTimeout
,其回呼函數被 Web API 呼叫。 (詳情請參考我關於event loop的文章.)
promise 和 timeout 都是異步操作,函數繼續執行當JS引擎忙於執行promise 和 處理 setTimeout
的呼叫。相當於 Last line!
首先被輸出, 因為它不是異步操作。執行完 funcOne
的最後一行,promise 狀態轉變為 resolved,Promise!
被輸出。然而,因為我們呼叫了 funcTwo()
, 呼叫堆疊不為空,setTimeout
的回呼仍不能入堆疊。
我們現在處於 funcTwo
,先 awaiting myPromise。通過 await
關鍵字, 我們暫停了函數的執行直到 promise 狀態變為 resolved (或 rejected)。然後,我們輸出 res
的 awaited 值(因為 promise 本身回傳一個 promise)。接著輸出 Promise!
。
下一行就是 異步操作 setTimeout
,其回呼函數被 Web API 呼叫。
我們執行到函數 funcTwo
的最後一行,輸出 Last line!
。現在,因為 funcTwo
出堆疊,呼叫堆疊為空。在事件佇列中等待的回呼函數(() => console.log("Timeout!")
from funcOne
, and () => console.log("Timeout!")
from funcTwo
)以此入堆疊。第一個回呼輸出 Timeout!
,並出堆疊。然後,第二個回呼輸出 Timeout!
,並出堆疊。得到結果 Last line! Promise! Promise! Last line! Timeout! Timeout!
// sum.js
export default function sum(x) {
return x + x;
}
// index.js
import * as sum from "./sum";
- A:
sum(4)
- B:
sum.sum(4)
- C:
sum.default(4)
- D: 預設導出不用
*
來導入,只能具名導出
答案
使用符號 *
,我們引入文件中的所有值,包括預設和具名。如果我們有以下文件:
// info.js
export const name = "Lydia";
export const age = 21;
export default "I love JavaScript";
// index.js
import * as info from "./info";
console.log(info);
將會輸出以下內容:
{
default: "I love JavaScript",
name: "Lydia",
age: 21
}
以 sum
為例,相當於以下形式引入值 sum
:
{ default: function sum(x) { return x + x } }
我們可以通過呼叫 sum.default
來呼叫該函數
const handler = {
set: () => console.log("Added a new property!"),
get: () => console.log("Accessed a property!")
};
const person = new Proxy({}, handler);
person.name = "Lydia";
person.name;
- A:
Added a new property!
- B:
Accessed a property!
- C:
Added a new property!
Accessed a property!
- D: 沒有任何輸出
答案
使用 Proxy 物件,我們可以給一個物件添加自定義行為。在這個 case,我們傳遞一個包含以下屬性的物件 handler
: set
and get
。每當我門 設置 屬性值時 set
被呼叫,每當我們 獲取 時 get
被呼叫。
第一個參數是一個空物件 {}
,作為 person
的值。對於這個物件,自定義行為被定義在物件 handler
。如果我們向物件 person
添加屬性,set
將被呼叫。如果我們獲取 person
的屬性, get
將被呼叫。
首先,我們向 proxy 物件(person.name = "Lydia"
)添加一個屬性 name
。 set
被呼叫並輸出 "Added a new property!"
。
然後,我們獲取 proxy 物件的一個屬性,物件 handler 的屬性 get
被呼叫。輸出 "Accessed a property!"
。
const person = { name: "Lydia Hallie" };
Object.seal(person);
- A:
person.name = "Evan Bacon"
- B:
person.age = 21
- C:
delete person.name
- D:
Object.assign(person, { age: 21 })
const person = {
name: "Lydia Hallie",
address: {
street: "100 Main St"
}
};
Object.freeze(person);
- A:
person.name = "Evan Bacon"
- B:
delete person.address
- C:
person.address.street = "101 Main St"
- D:
person.pet = { name: "Mara" }
答案
使用函數 Object.freeze
對一個物件進行 凍結。不能對屬性進行添加,修改,刪除。
然而,它僅 對物件進行 淺 凍結,意味著只有 物件中的 直接 屬性被凍結。如果屬性是另一個 object,像案例中的 address
,address
中的屬性沒有被凍結,仍然可以被修改。
const add = x => x + x;
function myFunc(num = 2, value = add(num)) {
console.log(num, value);
}
myFunc();
myFunc(3);
- A:
2
4
and3
6
- B:
2
NaN
and3
NaN
- C:
2
Error
and3
6
- D:
2
4
and3
Error
答案
首先我們不傳遞任何參數呼叫 myFunc()
。因為我們沒有傳遞參數,num
和 value
獲取它們各自的預設值:num 為 2
, 而 value
為函數 add
的回傳值。對於函數 add
,我們傳遞值為2的 num
作為參數。函數 add
回傳 4
作為 value
的值。
然後,我們呼叫 myFunc(3)
並傳遞值 3
參數 num
的值。我們沒有給 value
傳遞值。因為我們沒有給參數 value
傳遞值,它獲取預設值:函數 add
的回傳值。對於函數 add
,我們傳遞值為3的 num
給它。函數 add
回傳 6
作為 value
的值。
class Counter {
#number = 10
increment() {
this.#number++
}
getNum() {
return this.#number
}
}
const counter = new Counter()
counter.increment()
console.log(counter.#number)
- A:
10
- B:
11
- C:
undefined
- D:
SyntaxError
答案
在 ES2020 中,通過 #
我們可以給 class 添加私有變數。在 class 的外部我們無法存取該值。當我們嘗試輸出 counter.#number
,語法錯誤被拋出:我們無法在 class Counter
外部存取它!
const teams = [
{ name: "Team 1", members: ["Paul", "Lisa"] },
{ name: "Team 2", members: ["Laura", "Tim"] }
];
function* getMembers(members) {
for (let i = 0; i < members.length; i++) {
yield members[i];
}
}
function* getTeams(teams) {
for (let i = 0; i < teams.length; i++) {
// ✨ SOMETHING IS MISSING HERE ✨
}
}
const obj = getTeams(teams);
obj.next(); // { value: "Paul", done: false }
obj.next(); // { value: "Lisa", done: false }
- A:
yield getMembers(teams[i].members)
- B:
yield* getMembers(teams[i].members)
- C:
return getMembers(teams[i].members)
- D:
return yield getMembers(teams[i].members)
答案
為了遍歷 teams
陣列中物件的屬性 members
中的每一項,我們需要將 teams[i].members
傳遞給 Generator 函數 getMembers
。 Generator 函數回傳一個 generator 物件。為了遍歷這個 generator 物件中的每一項,我們需要使用 yield*
.
如果我們沒有寫 yield
,return yield
或者 return
,整個 Generator 函數不會第一時間 return 當我們呼叫 next
函數.
const person = {
name: "Lydia Hallie",
hobbies: ["coding"]
};
function addHobby(hobby, hobbies = person.hobbies) {
hobbies.push(hobby);
return hobbies;
}
addHobby("running", []);
addHobby("dancing");
addHobby("baking", person.hobbies);
console.log(person.hobbies);
- A:
["coding"]
- B:
["coding", "dancing"]
- C:
["coding", "dancing", "baking"]
- D:
["coding", "running", "dancing", "baking"]
答案
函數 addHobby
接受兩個參數,hobby
和有著物件 person
中陣列 hobbies
預設值的 hobbies
。
首相,我們呼叫函數 addHobby
,並給 hobby
傳遞 "running"
以及給 hobbies
傳遞一個空陣列。因為我們給 hobbies
傳遞了空陣列,"running"
被添加到這個空陣列。
然後,我們呼叫函數 addHobby
,並給 hobby
傳遞 "dancing"
。我們不向 hobbies
傳遞值,因此它獲取其預設值 —— 物件 person
的 屬性 hobbies
。我們向陣列 person.hobbies
push dancing
。
最後,我們呼叫函數 addHobby
,並向 hobby
傳遞 值 "bdaking"
,並且向 hobbies
傳遞 person.hobbies
。我們向陣列 person.hobbies
push dancing
。
pushing dancing
和 baking
之後,person.hobbies
的值為 ["coding", "dancing", "baking"]
class Bird {
constructor() {
console.log("I'm a bird. 🦢");
}
}
class Flamingo extends Bird {
constructor() {
console.log("I'm pink. 🌸");
super();
}
}
const pet = new Flamingo();
- A:
I'm pink. 🌸
- B:
I'm pink. 🌸
I'm a bird. 🦢
- C:
I'm a bird. 🦢
I'm pink. 🌸
- D: Nothing, we didn't call any method
答案
我們建立了class Flamingo
的實例 pet
。當我們實例化這個實例,Flamingo
中的 constructor
被呼叫。首相,輸出 "I'm pink. 🌸"
, 之後我們呼叫super()
。 super()
呼叫父class的建構函數,Bird
。 Bird
的建構函數被呼叫,並輸出 "I'm a bird. 🦢"
。
const emojis = ["🎄", "🎅🏼", "🎁", "⭐"];
/* 1 */ emojis.push("🦌");
/* 2 */ emojis.splice(0, 2);
/* 3 */ emojis = [...emojis, "🥂"];
/* 4 */ emojis.length = 0;
- A: 1
- B: 1 and 2
- C: 3 and 4
- D: 3
答案
const
關鍵字意味著我們不能 重定義 變數中的值,它 僅可讀。而然,值本身不可修改。陣列 emojis
中的值可被修改,如 push 新的值, 拼接,又或者將陣列的長度設置為0。
const person = {
name: "Lydia Hallie",
age: 21
}
[...person] // ["Lydia Hallie", 21]
- A: 不需要,物件預設就是可迭代的
- B:
*[Symbol.iterator]() { for (let x in this) yield* this[x] }
- C:
*[Symbol.iterator]() { for (let x in this) yield* Object.values(this) }
- D:
*[Symbol.iterator]() { for (let x in this) yield this }
答案
物件預設並不是可迭代的。如果迭代規則被定義,則一個物件是可迭代的(An iterable is an iterable if the iterator protocol is present)。我們可以通過添加迭代器symbol [Symbol.iterator]
來定義迭代規則,其回傳一個 generator 物件,比如說構建一個 generator 函數 *[Symbol.iterator]() {}
。如果我們想要回傳陣列 ["Lydia Hallie", 21]
: yield* Object.values(this)
,這個 generator 函數一定要 yield 物件 person
的Object.values
。
let count = 0;
const nums = [0, 1, 2, 3];
nums.forEach(num => {
if (num) count += 1
})
console.log(count)
- A: 1
- B: 2
- C: 3
- D: 4
答案
在 forEach
循環內部的 if
會判斷 num
的值是truthy或者是falsy。因為 nums
陣列的第一個數字是 0
,一個falsy值, if
語句代碼塊不會被執行。 count
僅僅在 nums
陣列的其他3個數字 1
,2
,3
時加1。因為 count
執行了3次加 1
運算,所以 count
的值為 3
。
function getFruit(fruits) {
console.log(fruits?.[1]?.[1])
}
getFruit([['🍊', '🍌'], ['🍍']])
getFruit()
getFruit([['🍍'], ['🍊', '🍌']])
- A:
null
,undefined
, 🍌 - B:
[]
,null
, 🍌 - C:
[]
,[]
, 🍌 - D:
undefined
,undefined
, 🍌
答案
?
允許我們去選擇性地訪問物件內部更深層的嵌套屬性。我們嘗試輸出 fruits
陣列索引值為 1
的子陣列內部的索引值為 1
的元素。如果在 fruits
陣列索引值 為 1
的位置不存在元素,會直接回傳 undefined
。如果 fruits
陣列在索引值為 1
的位置存在元素,但是子陣列在索引值為 1
的位置不存在元素,也會回傳 undefined
。
首先,我們嘗試輸出 [['🍊', '🍌'], ['🍍']]
的子陣列 ['🍍']
的第2個元素。這個子陣列只包含一個元素,也就意味著在索引值為 1
的位置不存在元素,所以回傳的是 undefined
。
其次,我們在沒有傳入任何參數呼叫了 getFruits
函數,也就意味著形參 fruits
的預設值為undefined
。因為我們選擇性地鏈接了 fruits
在索引值為 1
的元素,因為在索引值為 1
的位置不存在元素,因此回傳的是 undefined
。
最後,我們嘗試輸出 ['🍍'], ['🍊', '🍌']
的子陣列 ['🍊', '🍌']
的第2個元素。子陣列索引值為 1
的位置為 🍌
,因此它被輸出出了。
class Calc {
constructor() {
this.count = 0
}
increase() {
this.count ++
}
}
const calc = new Calc()
new Calc().increase()
console.log(calc.count)
- A:
0
- B:
1
- C:
undefined
- D:
ReferenceError
答案
我們設置 calc
變數為 Calc
類的一個新實例。然後,我們初始化一個 Calc
的新實例,而且呼叫了這個實例的 increase
函數。因為count屬性是在 Calc
class的constructor內部的,所以count屬性不會在 Calc
的原型鏈上共享出去。這就意味著calc實例的count值不會被更新,count仍然是 0
。
const user = {
email: "[email protected]",
password: "12345"
}
const updateUser = ({ email, password }) => {
if (email) {
Object.assign(user, { email })
}
if (password) {
user.password = password
}
return user
}
const updatedUser = updateUser({ email: "[email protected]" })
console.log(updatedUser === user)
- A:
false
- B:
true
- C:
TypeError
- D:
ReferenceError
答案
updateUser
函數更新user的 email
和 password
屬性的值, 如果它們的值傳入函數, 函數回傳的就是 user
物件。 updateUser
函數的回傳值是 user
物件,意味著updatedUser的值與 user
指向的是同一個 user
物件。 updatedUser === user
為 true
.
const fruit = ['🍌', '🍊', '🍎']
fruit.slice(0, 1)
fruit.splice(0, 1)
fruit.unshift('🍇')
console.log(fruit)
- A:
['🍌', '🍊', '🍎']
- B:
['🍊', '🍎']
- C:
['🍇', '🍊', '🍎']
- D:
['🍇', '🍌', '🍊', '🍎']
答案
首先,我們在fruit陣列上呼叫 slice
函數。 slice函數不會修改原始陣列,但是會回傳從陣列切片下來的值:香蕉emoji。
其次,我們在fruit陣列上呼叫 splice
函數。 splice函數會修改原始陣列,也就意味著fruit陣列此時為 ['🍊', '🍎']
。
最後,我們在fruit陣列上呼叫 unshift
函數,通過添加一個值的方式改變了原始陣列,添加的是'🍇',它成為了陣列的第一個元素。現在fruit陣列的組成為 ['🍇', '🍊', '🍎']
。
const animals = {};
let dog = { emoji: '🐶' }
let cat = { emoji: '🐈' }
animals[dog] = { ...dog, name: "Mara" }
animals[cat] = { ...cat, name: "Sara" }
console.log(animals[dog])
- A:
{ emoji: "🐶", name: "Mara" }
- B:
{ emoji: "🐈", name: "Sara" }
- C:
undefined
- D:
ReferenceError
答案
物件的鍵會被轉換為字符串。
因為 dog
的值是一個物件, animals[dog]
實際上意味著我們建立了一個叫做 "object Object"
的屬性來代表新的物件。 animals["object Object"]
現在等於 { emoji: "🐶", name: "Mara"}
。
cat
也是一個物件,animals[cat]
實際上意味著我們在用新的cat的屬性覆蓋 animals[``"``object Object``"``]
的值。
輸出animals[dog]
,實際上是animals["object Object"]
,這是因為轉化dog
物件為一個字符串結果"object Object"
,所以回傳{ emoji: "🐈" , name: "Sara" }
。
const user = {
email: "[email protected]",
updateEmail: email => {
this.email = email
}
}
user.updateEmail("[email protected]")
console.log(user.email)
- A:
[email protected]
- B:
[email protected]
- C:
undefined
- D:
ReferenceError
答案
updateEmail
函數是一個箭頭函數,它沒有和 user
物件綁定。這就意味著 this
關鍵字不會引用到 user
物件,但是會引用到全局物件。 user
物件內部的 email
的值不會更新。當輸出 user.email
的時候, 原始值 [email protected]
被回傳。
const promise1 = Promise.resolve('First')
const promise2 = Promise.resolve('Second')
const promise3 = Promise.reject('Third')
const promise4 = Promise.resolve('Fourth')
const runPromises = async () => {
const res1 = await Promise.all([promise1, promise2])
const res2 = await Promise.all([promise3, promise4])
return [res1, res2]
}
runPromises()
.then(res => console.log(res))
.catch(err => console.log(err))
- A:
[['First', 'Second'], ['Fourth']]
- B:
[['First', 'Second'], ['Third', 'Fourth']]
- C:
[['First', 'Second']]
- D:
'Third'
答案
Promise.all
函數可以並行式執行promise。如果其中一個promise失敗了,Promise.all
函數會帶上被reject的promise的值_rejects_。在這個例子中, promise3
帶著 "Third"
值reject。我們在呼叫 runPromises
時在 runPromises
函數內部的 catch
函數去捕獲任意error從而捕獲到被reject的值。因為 promise3
帶著 "Third"
被reject,所以只有 "Third"
輸出。
const keys = ["name", "age"]
const values = ["Lydia", 22]
const method = /* ?? */
Object[method](keys.map((_, i) => {
return [keys[i], values[i]]
})) // { name: "Lydia", age: 22 }
- A:
entries
- B:
values
- C:
fromEntries
- D:
forEach
答案
fromEntries
函數可以將二維陣列轉換為物件。在每個子陣列的第一個元素是key,在每個子陣列的第二個元素是value。在這個例子中,我們映射了 keys
陣列,它回傳了一個陣列,陣列的第一個元素為keys陣列當前索引的值,第二個元素為values陣列當前索引的值。
這樣就建立了一個包含正確keys和values的子陣列的陣列,因此結果為{ name: "Lydia", age: 22 }
。
const createMember = ({ email, address = {}}) => {
const validEmail = /.+\@.+\..+/.test(email)
if (!validEmail) throw new Error("Valid email pls")
return {
email,
address: address ? address : null
}
}
const member = createMember({ email: "[email protected]" })
console.log(member)
- A:
{ email: "[email protected]", address: null }
- B:
{ email: "[email protected]" }
- C:
{ email: "[email protected]", address: {} }
- D:
{ email: "[email protected]", address: undefined }
答案
address
的預設值是一個空物件 {}
。當我們設置 member
變數為 createMember
函數回傳的物件,我們沒有為address參數傳值,意味著address的值為預設的空物件 {}
。一個空物件是一個truthy值,意味著 address ? address : null
條件會回傳 true
。 address的值為空物件 {}
。
let randomValue = { name: "Lydia" }
randomValue = 23
if (!typeof randomValue === "string") {
console.log("It's not a string!")
} else {
console.log("Yay it's a string!")
}
- A:
It's not a string!
- B:
Yay it's a string!
- C:
TypeError
- D:
undefined
答案
if
語句的條件判斷 !typeof randomValue
的值是否等於 "string"
。 !
操作符將這個值轉化為一個布林值。如果值是truthy的話,回傳值會是 false
,如果值是falsy,回傳值會是 true
。在這裡, typeof randomValue
的回傳值是一個truthy值 "number"
,意味著 !typeof randomValue
的值是一個布林值 false
。
!typeof randomValue === "string"
總是回傳false,因為我們實際上是在執行 false === "string"
。因為條件回傳的是 false
,所以 else
語句中的代碼塊會被執行,因此輸出 Yay it's a string!
。