Hàng ngày tôi sẽ đăng các câu hỏi multichoice lên Instagram của tôi và tại đây nữa!
Các câu hỏi sẽ từ cơ bản đến nâng cao: kiểm tra trình độ JavaScript của bạn, làm mới một chút về kiến thức, hay chuẩn bị cho cuộc phỏng vấn lập trình! 💪 🚀 Tôi sẽ cập nhật các câu hỏi mới hàng tuần.
Các đáp án được đặt dưới mỗi câu hỏi, hãy click để tham khảo chúng. Chúc may mắn ❤️
Danh sách các ngôn ngữ khác:
- English
- 中文版本
- Versión en español
- 日本語
- 한국어
- Русский
- Western Balkan
- Deutsch
- Українська мова
- Português Brasil
- العربية
- اللغة العامية - Egyptian Arabic
function sayHi() {
console.log(name);
console.log(age);
var name = "Lydia";
let age = 21;
}
sayHi();
- A:
Lydia
vàundefined
- B:
Lydia
vàReferenceError
- C:
ReferenceError
và21
- D:
undefined
vàReferenceError
Đáp án
Trong hàm chúng ta đã khai báo biến name
với var
. Điều đó có nghĩa là biến này sẽ được hoisted (một vùng nhớ sẽ được set up khi biến được khởi tạo) với giá trị mặc định là undefined
, cho tới khi chúng ta thực sự định nghĩa biến đó. Trong hàm này, chúng ta chưa hề định nghĩa biến name
tại dòng mà ta log ra, vậy nên giá trị mặc định của nó vẫn là undefined
.
Các biến được khai báo với keyword let
(và const
) cũng được hoisted nhưng không giống như var
, chúng không được khởi tạo. Chúng ta sẽ không thể truy cập chúng cho tới khi chúng ta khai báo (khởi tạo) chúng. Người ta gọi đó là "temporal dead zone". Khi ta truy cập đến một giá trị trước khi chúng được khai báo, JavaScript sẽ throws một 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
and0 1 2
- B:
0 1 2
and3 3 3
- C:
3 3 3
and0 1 2
Đáp án
Bởi vì event queue trong JavaScript, hàm setTimeout
callback sẽ được gọi sau khi vòng lặp được thực hiện. Bời vì biến i
trong vòng lặp đầu tiên được khai báo với từ khóa var
, nên nó sẽ là một biến global. Trong suốt vòng lặp, mỗi lần chúng ta tăng giá trị của i
lên 1
, sử dụng phép toán ++
. Cho tới khi callback setTimeout
được gọi, giá trị của i
đã trở thành 3
rồi.
Trong vòng lặp thứ 2, biến i
được khai báo với từ khóa let
, có nghĩa nó là một biến block-scoped (block là những gì được viết bên trong cặp ngoặc { }
). Tại mỗi vòng lặp, i
sẽ là một biến mới có một giá trị mới, và giá trị đó có scope là bên trong vòng lặp mà thô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
Đáp án
Chú ý rằng giá trị diameter
là một hàm thông thường, còn perimeter
là một arrow function.
Không giống như hàm thông thường, với arrow function, biếnthis
sẽ trỏ tới surrounding scope! Có nghĩa là khi chúng ta gọi perimeter
, nó sẽ không được gọi bởi shape object, mà nó được gọi bởi object nào đó tại surrounding scope (ví dụ window
chẳng hạn).
Khi không có giá trị radius
tại object đó, nó sẽ trả về undefined
.
+true;
!"Lydia";
- A:
1
andfalse
- B:
false
andNaN
- C:
false
andfalse
Đáp án
Phép toán cộng +
sẽ convert một toán hạng sang dạng number. true
là 1
, và false
is 0
.
Chuỗi 'Lydia'
là một truthy value. Điều chúng ta thật sự đang hỏi chính là "có phải một giá trị truthy là falsy?". Rõ ràng câu trả lời là false
rồi.
const bird = {
size: "small"
};
const mouse = {
name: "Mickey",
small: true
};
- A:
mouse.bird.size
không hợp lệ - B:
mouse[bird.size]
không hợp lệ - C:
mouse[bird["size"]]
không hợp lệ - D: Tất cả đều hợp lệ
Đáp án
Trong JavaScript thì tất cả keys của các object đều là string (ngoại trừ khi nó là một Symbol). Dù chúng ta không viết chúng như một string, về cơ bản chúng sẽ luôn được chuyển sang dạng string.
JavaScript thông dịch (hay unboxes) từng câu lệnh. Khi chúng ta sử dụng cặp dấu ngoặc []
, nó sẽ tìm kiếm dấu mở ngoặc đầu tiên [
, và sẽ tiếp tục tìm kiếm cho tới khi gặp dấu đóng ngoặc ]
. Chỉ khi đó thì câu lệnh mới được thực thi.
mouse[bird.size]
: Giá trị đầu tiên bird.size
là "small"
. mouse["small"]
sẽ trả về true
Tuy nhiên, khi chúng ta sử dụng dấu chấm .
, điều trên không còn đúng nữa. mouse
không hề có key nào tên là bird
, có nghĩa mouse.bird
sẽ là undefined
. Sau đó chúng ta gọi size
sử dụng chấm .
: mouse.bird.size
. Vì mouse.bird
là undefined
, lời gọi sẽ trở thành undefined.size
. Đây là một lời gọi không hợp lệ, nó sẽ throw ra một lỗi kiểu như Cannot read property "size" of undefined
.
let c = { greeting: "Hey!" };
let d;
d = c;
c.greeting = "Hello";
console.log(d.greeting);
- A:
Hello
- B:
Hey
- C:
undefined
- D:
ReferenceError
- E:
TypeError
Đáp án
Trong JavaScript, tất cả các object sẽ được tham chiếu khi chúng được gán _bằng_wwwww một giá trị khác.
Đầu tiên, giá trị c
có giá trị là một object. Sau đó, chúng ta gán d
tham chiếu tới object mà c
trỏ tới.
Khi ta thay đổi giá trị của object, tất cả các biến tham chiếu cũng đều thay đổi giá trị theo.
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
Đáp án
new Number()
là một hàm built-in constructor. Mặc dù nó trông có vẻ giống như là một số, nhưng không phải: nó thực sự là một object với hàng tá những thông số khác nữa.
Khi ta sử dụng phép so sánh ==
, nó đơn thuần chỉ kiểm tra xem 2 biến có giá trị giống nhau. Chúng đều có giá trị là 3
, vậy nên phép toán đầu trả về true
.
Tuy nhiên khi sử dụng phép so sánh ===
, cả giá trị và kiểu đều phải giống nhau. Rõ ràng: new Number()
không phải là một số, nó là một object. Cả 2 phép toán sau đều trả về 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
Đáp án
Hàm colorChange
là một hàm static (hàm tĩnh). Hàm static được thiết kế để chỉ để tồn tại ở mức class, và không thể truyền cho bất cứ instance con nào. Vì freddie
là một instance con, hàm static này sẽ không được truyền xuống, và do đó không thể gọi được tại freddie
instance: nó sẽ throw ra một TypeError
.
let greeting;
greetign = {}; // Lỗi đánh máy!
console.log(greetign);
- A:
{}
- B:
ReferenceError: greetign is not defined
- C:
undefined
Đáp án
Nó sẽ log ra object greetign
, bởi vì chúng ta vừa khởi tạo một global object! Khi chúng ta đánh máy nhầm greeting
thành greetign
, trình thông dịch của JS sẽ coi nó như là global.greetign = {}
(hay window.greetign = {}
nếu chạy trên browser).
Để tránh điều này chúng ta có thể sử dụng "use strict"
. Nó sẽ đảm bảo rẳng các biến đều phải được khai báo trước khi sử dụng.
function bark() {
console.log("Woof!");
}
bark.animal = "dog";
- A: Hoàn toàn không có vấn đề gì!
- B:
SyntaxError
. Bạn không thể thêm thuộc tính theo cách này. - C:
undefined
- D:
ReferenceError
Đáp án
Điều này là có thể với Javascript, bởi vì function
cũng chỉ là object
mà thôi! (Mọi primitive types đều là object)
Function là một object đặc biệt. Phần code mà bạn viết không phải là function thực tế đâu. Function ở đây chính là một object với các thuộc tính. Và các thuộc tính này có thể gọi được.
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
Đáp án
Chúng ta không thể add thêm một thuộc tính cho một constructor giống như một object thông thường. Nếu bạn muốn add thêm thuộc tính nào đó cho tất cả các object một lần, bạn phải dùng prototype
. Trong trường hợp này cũng vậy.
Person.prototype.getFullName = function() {
return `${this.firstName} ${this.lastName}`;
};
khi này member.getFullName()
sẽ hoạt động. Tại sao nên làm vậy? Hãy thử thêm chúng trực tiếp vào constructor xem sao. Không phải mọi instance Person
đều cần phương thức này. Nó sẽ dẫn tới việc lãng phí rất nhiều bộ nhớ, khi chúng đều phải lưu trữ thuộc tính này cho mỗi instance. Thay vì thế, nếu ta chỉ thêm chúng vào prototype
, ta sẽ chỉ tốn bộ nhớ một lần mà thôi, và mọi object khác đều có thể truy cập đến nó!
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"}
vàundefined
- B:
Person {firstName: "Lydia", lastName: "Hallie"}
vàPerson {firstName: "Sarah", lastName: "Smith"}
- C:
Person {firstName: "Lydia", lastName: "Hallie"}
và{}
- D:
Person {firstName: "Lydia", lastName: "Hallie"}
vàReferenceError
Đáp án
Với sarah
, chúng ta khai báo mà không có từ khóa new
. Khi sử dụng new
, nó sẽ trỏ đến một object mới mà ta vừa tạo ra. Tuy nhiên nếu ta không dùng new
thì nó sẽ trỏ tới global object!
Chúng ta cho rằng this.firstName
là "Sarah"
và this.lastName
là "Smith"
. Tuy nhiên sự thực là chúng ta đã định nghĩa global.firstName = 'Sarah'
và global.lastName = 'Smith'
. Bản thân biến sarah
vẫn là undefined
.
- A: Target > Capturing > Bubbling
- B: Bubbling > Target > Capturing
- C: Target > Bubbling > Capturing
- D: Capturing > Target > Bubbling
Đáp án
Trong capturing phase, event được truyền từ các phần tử cha cho tới phần tử target. Sau khi tới được phần tử target thì bubbling sẽ bắt đầu.
- A: đúng
- B: sai
Đáp án
Tất cả các object đều có prototypes, ngoại trừ base object. Object base có thể truy cập đến vài methods và properties, ví dụ như .toString
. Đó là lý do tại sao chúng ta có thể sử dụng được các built-in methods trong JavaScript! Tất cả các phương thức đó đều có trong prototype. Mặc dù JavaScript không thể tìm thấy chúng trong object một cách trực tiếp, nó sẽ được truyền xuống thông qua prototype chain và xuống tới object, tại đây chúng ta có thể truy cập được nó.
function sum(a, b) {
return a + b;
}
sum(1, "2");
- A:
NaN
- B:
TypeError
- C:
"12"
- D:
3
Đáp án
JavaScript là một ngôn ngữ dynamically typed: chúng ta không khai báo kiểu dữ liệu khi khai báo biến. Giá trị có thể bị tự động convert sang một kiểu dữ liệu khác mà ta không hề hay biết, điều này được gọi là implicit type coercion. Coercion có nghĩa là convert từ kiểu này sang kiểu khác.
Trong ví dụ này, JavaScript sẽ convert số 1
sang dạng string. Mỗi khi ta cộng một số (1
) với một string ('2'
), số sẽ luôn được xem như là một string. Kết quả sẽ là một phép nối chuỗi giống như "Hello" + "World"
, vậy nên "1" + "2"
sẽ trả về là "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
Đáp án
Khi phép toán ++
nằm ở đằng sau (postfix):
- Trả về giá trị (trả về
0
) - Tăng giá trị lên (number giờ là
1
)
Khi phép toán ++
nằm ở đằng trước (prefix):
- Tăng giá trị lên (number giờ là
2
) - Trả về giá trị (trả về
2
)
Vậy kết quả là 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
Đáp án
Nếu bạn dùng tagged template literals, giá trị của đối số đầu tiên luôn luôn là một mảng các string. Những đối số còn lại sẽ lấy giá trị từ biểu thức đưa vào!
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
Đáp án
Khi test sự bằng nhau, các kiểu dữ liệu cơ bản sẽ so sánh giá trị của chúng, còn object thì so sánh tham chiếu. JavaScript sẽ kiểm tra xem các object đó có trỏ đến những vùng nhớ giống nhau hay không.
Hai object chúng ta đang so sánh không có được điều đó: object đối số tham chiếu đến một vùng nhớ khác với object chúng ta dùng để kiểm tra sự bằng nhau.
Đó là lý do tại sao cả { age: 18 } === { age: 18 }
và { age: 18 } == { age: 18 }
đều trả về false
.
function getAge(...args) {
console.log(typeof args);
}
getAge(21);
- A:
"number"
- B:
"array"
- C:
"object"
- D:
"NaN"
Đáp án
Spread operator (...args
.) sẽ trả về một mảng các đối số. Mảng thực chất là một object, vậy nên typeof args
sẽ trả về "object"
.
function getAge() {
"use strict";
age = 21;
console.log(age);
}
getAge();
- A:
21
- B:
undefined
- C:
ReferenceError
- D:
TypeError
Đáp án
Với "use strict"
, chúng ta sẽ đảm bảo được rằng ta sẽ không bao giờ khai báo biến global một cách vô ý. Tại đây chúng ta chưa khai báo biến age
, và khi dùng "use strict"
, nó sẽ throw ra một reference error. Nếu như không dùng "use strict"
, nó sẽ vẫn hoạt động, vì thuộc tính age
sẽ được thêm vào global object.
const sum = eval("10*10+5");
- A:
105
- B:
"105"
- C:
TypeError
- D:
"10*10+5"
Đáp án
eval
sẽ đánh giá đoạn code bên trong string. Nếu nó là một biểu thức, giống như trong trường hợp này, nó sẽ tính toán giá trị đó. Biểu thức là 10 * 10 + 5
, kết quả sẽ là 105
.
sessionStorage.setItem("cool_secret", 123);
- A: Mãi mãi, dữ liệu sẽ không bao giờ mất.
- B: Khi user đóng tab lại.
- C: Khi user không chỉ là đóng tab, mà đóng browser lại.
- D: Khi user tắt máy tính đi.
Đáp án
Dữ liệu được lưu trữ trong sessionStorage
sẽ được xóa khi đóng tab.
Nếu chúng ta dùng localStorage
, data sẽ được lưu trữ mãi mãi, trừ phi hàm localStorage.clear()
được gọi.
var num = 8;
var num = 10;
console.log(num);
- A:
8
- B:
10
- C:
SyntaxError
- D:
ReferenceError
Đáp án
Với từ khóa var
bạn có thể khai báo bao nhiêu biến trùng tên tùy thích. Biến đó sẽ có giá trị là lần cuối khai báo.
Bạn không thể làm điều tương tự với let
hay const
vì chúng là 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
Đáp án
Tất cả các keys của object (ngoại trừ Symbols) về bản chất đều là string, dù chúng ta có viết chúng ra dạng string hay không. Nó là lý do tại sao obj.hasOwnProperty('1')
cũng trả về true
.
Tuy nhiên điều đó không đúng với set
. Không hề có '1'
trong set của chúng ta: set.has('1')
trả về false
. Có số 1
trong set, nên set.has(1)
trả về 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
Đáp án
Nếu chúng ta có 2 keys giống nhau, thì chúng sẽ bị replace. Nó sẽ vẫn nằm ở vị trí đầu tiên chúng được khai báo, nhưng giá trị thì sẽ là giá trị lần cuối.
26. Ngữ cảnh thực thi global của JavaScript tạo ra 2 thứ cho chúng ta: global object, và từ khóa "this".
- A: đúng
- B: sai
- C: còn tùy
Đáp án
Ngữ cảnh thực thi cơ bản chính là ngữ cảnh global: nó là thứ mà chúng ta có thể truy cập được ở bất cứ đâu trong code.
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
Đáp án
String
là một built-in constructor, có nghĩa là chúng ta có thể thêm các thuộc tính vào đó. Ta vừa thêm vào đó một thuộc tính. Kiểu String cơ bản sẽ được convert sang dạng object bởi hàm string prototype. Vì thế nên tất cả các string object đều có thể truy cập đến hàm giveLydiaPizza
này!
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
Đáp án
Object keys sẽ tự động được convert sang dạng string. Chúng ta đang set một object như là một key cho object a
, với giá trị là 123
.
Tuy nhiên khi ta string hóa một object, nó sẽ trở thành "[Object object]"
. Nên tại đây phép gán này thực chất chính là a["Object object"] = 123
. Phép gán tiếp theo cũng giống hệt vậy. c
là một object khác mà chúng ta đang string hóa nó. Theo đó, a["Object object"] = 456
.
Cuối cùng khi gọi a[b]
, thực chất chính là gọi a["Object object"]
. Giá trị của nó là 456
, nên trả về là 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
Đáp án
Chúng ta có một hàm setTimeout
được gọi đầu tiên. Nên, nó sẽ được log ra cuối cùng.
Điều đó bởi vì trên browser, chúng ta không hề có runtime engine, đơn thuần chỉ có WebAPI
. WebAPI
cho chúng ta một hàm setTimeout
, ta hãy ví dụ nó trên DOM.
Sau khi callback được gửi tới WebAPI, bản thân hàm setTimeout
(nhưng không phải callback nhé!) sẽ được đưa ra khỏi stack.
Giờ đây, foo
được gọi, và "First"
được log ra.
foo
được đưa ra khỏi stack, và baz
được gọi. "Third"
được log ra.
WebAPI không thể thêm thứ gì đó vào stack cho tới khi nó được sẵn sàng. Thay vào đó, nó đẩy callback function đến một nơi gọi là queue.
Đó chính là nơi mà event loop làm việc. Một event loop sẽ quan sát stack và task queue. Nếu stack trở nên rỗng, nó sẽ lấy giá trị đầu tiên trong queue để đưa tiếp vào stack.
bar
được gọi, "Second"
được log ra, và nó được đưa ra khỏi stack.
<div onclick="console.log('first div')">
<div onclick="console.log('second div')">
<button onclick="console.log('button')">
Click!
</button>
</div>
</div>
- A: Outer
div
- B: Inner
div
- C:
button
- D: Một mảng với toàn bộ các phần tử lồng nhau.
Đáp án
Phần tử sâu nhất trong các phần tử lồng nhau sẽ là target của event. Bạn có thể ngăn việc đó lại bằng cách sử dụng event.stopPropagation
<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 án
Nếu ta click vào p
, ta sẽ thấy hai log: p
và div
. Trong chuỗi event sẽ có 3 phases: capturing, target, và bubbling. Mặc định thì event handlers sẽ được thực hiện tại bubbling phase (trừ phi chúng ta khai báo useCapture
là true
). Chúng sẽ đi từ phần tử sâu nhất ra đến bên ngoài.
const person = { name: "Lydia" };
function sayHi(age) {
console.log(`${this.name} is ${age}`);
}
sayHi.call(person, 21);
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
Đáp án
Với cả hai, chúng ta có thể đưa vào object để sử dụng từ khóa this
. Tuy nhiên, .call
có nghĩa là thực hiện ngay lập tức!
.bind.
trả về một bản copy của function, với context kèm theo! Nó sẽ không thực hiện ngay lập tức.
function sayHi() {
return (() => 0)();
}
typeof sayHi();
- A:
"object"
- B:
"number"
- C:
"function"
- D:
"undefined"
Đáp án
sayHi
function trả về giá trị của một hàm gọi ngay lập tức (immediately invoked function - IIFE). Function này trả về 0
, kiểu dữ liệu "number"
.
FYI: chỉ có 7 kiểu dữ liệu built-in: null
, undefined
, boolean
, number
, string
, object
, and symbol
. "function"
không phải là một kiểu, mà là objects, kiểu dữ liệu là "object"
.
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: Tất cả đều là falsy
Đáp án
Chỉ có 6 giá trị falsy:
undefined
null
NaN
0
''
(chuỗi rỗng)false
Function constructors như new Number
và new Boolean
đều là 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
Đáp án
Mỗi khi chúng ta set một giá trị cho một phần tử trong mảng mà vượt quá độ dài của mảng, JavaScript sẽ tạo ra những "empty slots". Chúng sẽ có giá trị là undefined
, nhưng khi ghi ra thì chúng ta sẽ thấy dạng:
[1, 2, 3, 7 x empty, 11]
phụ thuộc vào nơi mà chúng ta chạy chúng (có thể khác nhau tùy môi trường browser, node, etc.)
(() => {
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
Đáp án
catch
block nhận về giá trị x
. Đây không phải là giá trị x
mà ta khai báo với từ khóa let
ở bên trên. Đây là biến x
trong block-scoped.
Tiếp đó, chúng ta set giá trị của biến block-scoped này là 1
, và đồng thời cũng set giá trị cho biến y
. Giờ đây chúng ta log ra giá trị của biến block-scoped variable x
, bằng 1
.
Bên ngoài catch
block, x
vẫn là undefined
, và y
là 2
. Khi gọi console.log(x)
bên ngoài catch
block, nó sẽ trả về undefined
, và y
trả về 2
.
- A: primitives hoặc object
- B: function hoặc object
- C: hỏi mẹo khó đấy! chỉ object thôi
- D: number hoặc object
Đáp án
JavaScript chỉ có primitive types và objects.
Primitive types là boolean
, null
, undefined
, bigint
, number
, string
, and symbol
.
Điểm khác nhau giữa primitive và object chính là primitives không có thuộc tính hay phương thức; tuy nhiên, chúng ta để ý rằng là 'foo'.toUpperCase()
sẽ trả về 'FOO'
mà không phải là một TypeError
. Đó là bởi vì khi chúng ta truy cập các thuộc tính hay phương thức của một primitive như là string, JavaScript sẽ ngầm bao nó bởi một wrapper class
, ví dụ String
, và sau đó sẽ hủy việc bao đó ngay sau khi thực hiện xong. Mọi primitives ngoại trừ null
và undefine
đều hoạt động giống như vậy.
[[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]
Đáp án
[1, 2]
là giá trị khởi tạo. Đây là giá trị chúng ta bắt đầu, và cũng là giá trị đầu tiên của acc
. Tại vòng đầu tiên, acc
là [1,2]
, và cur
là [0, 1]
. Ta nối chúng lại tạo ra [1, 2, 0, 1]
.
Tiếp tục, [1, 2, 0, 1]
là acc
và [2, 3]
là cur
. Ta nối chúng lại tạo ra [1, 2, 0, 1, 2, 3]
.
!!null;
!!"";
!!1;
- A:
false
true
false
- B:
false
false
true
- C:
false
true
true
- D:
true
true
false
Đáp án
null
là falsy. !null
trả về true
. !true
trả về false
.
""
là falsy. !""
trả về true
. !true
trả về false
.
1
là truthy. !1
trả về fa�se
. !false
trả về true
.
setInterval(() => console.log("Hi"), 1000);
- A: một id duy nhất
- B: số lượng milliseconds
- C: function truyền vào
- D:
undefined
Đáp án
Nó trả về một id duy nhất. Id này dùng để clear interval sau này với hàm clearInterval()
.
[..."Lydia"];
- A:
["L", "y", "d", "i", "a"]
- B:
["Lydia"]
- C:
[[], "Lydia"]
- D:
[["L", "y", "d", "i", "a"]]
Đáp án
String là một iterable. Thế nên spread operator sẽ map toàn bộ các ký tự trong chuỗi lại thành một mảng.
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
Đáp án
Một hàm bình thường không thể bị dừng giữa chừng khi được gọi. Tuy nhiên một generator thì khác, nó có thể "dừng lại" được, và sau đó nó sẽ tiếp tục từ vị trí nó dừng lại. Mỗi khi một generator gặp một từ khóa yield
, nó sẽ sinh ra giá trị ngay phía sau nó. Chú ý là generator không trả về giá trị, nó sinh ra giá trị.
Đầu tiên, chúng ta khởi tạo generator với giá trị i
là 10
. Generator được gọi bằng cách sử dụng phương thức next()
. Khi lần đầu gọi thì i
vẫn là 10
. Khi nó bắt gặp từ khóa yield
: nó sẽ sinh ra giá trị i
. Generator sẽ được "tạm dừng" tại đây, và ghi ra giá trị 10
.
Sau đó chung ta tiếp tục gọi generator bằng cách sử dụng tiếp phương thức next()
. Nó sẽ bắt đầu từ vị trí nó tạm dừng lúc trước, khi i
vẫn đang là 10
. Và khi nó bắt gặp từ khóa yield
, nó sẽ sinh ra giá trị i * 2
. i
là 10
, nên nó sẽ sinh ra 10 * 2
, tức 20
. Vậy kết quả cuối cùng là 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"
Đáp án
Khi chúng ta đưa các promise vào trong một hàm Promise.race
, nó sẽ chỉ resolves hay rejects promise đầu tiên được resolves/rejects. Với hàm setTimeout
, chúng ta đưa vào một khoảng thời gian: 500 mili giây cho promise đầu tiên (firstPromise
), và 100 mili giây cho promise thứ hai (secondPromise
). Nó có nghĩa là secondPromise
sẽ hoàn thành trước và trả về giá trị 'two'
. res
khi này sẽ nhận giá trị 'two'
và được in ra console.
let person = { name: "Lydia" };
const members = [person];
person = null;
console.log(members);
- A:
null
- B:
[null]
- C:
[{}]
- D:
[{ name: "Lydia" }]
Đáp án
Đầu tiên, chúng ta khai báo một biến person
là một object có thuộc tính name
.
Sau đó chúng ta khai báo một biến members
. Ta set giá trị đầu tiên của mảng là giá trị của biến person
. Khi sử dụng gán bằng, object sẽ được tham chiếu tới object mà nó được gán. Khi ta gán tham chiếu từ một biến sang biến khác, ta tạo ra một bản sao của tham chiếu đó. (nên nhớ rằng đó vẫn là 2 tham chiếu hoàn toàn khác nhau!)
Sau đó ta set giá trị của person
bằng null
.
Chúng ta chỉ đơn thuần là thay đổi giá trị của biến person
mà thôi, chứ không phải giá trị của phần tử đầu tiên ở trong mảng, vì chúng ta có một tham chiếu khác đến object đó. Phần tử đầu tiên của mảng members
vẫn giữ tham chiêu đến object gốc. Do vậy, khi chúng ta in ra mảng members
, phần tử đầu tiên sẽ vẫn in ra giá trị của objet gốc.
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]
Đáp án
Với vòng lặp for-in
chúng ta sẽ lặp qua tất cả các keys
của object, trong trường hợp này là name
và age
. Về cơ bản, object keys là string (nếu nó không phải là Symbol). Tại mỗi vòng lặp, giá trị của item
chính là giá trị của key hiện tại trong vòng lặp. Đầu tiên, item
là name
, và được in ra. Vòng lặp sau, item
là age
, và được in ra.
console.log(3 + 4 + "5");
- A:
"345"
- B:
"75"
- C:
12
- D:
"12"
Đáp án
Compiler sẽ đánh giá biểu thức dựa trên độ ưu tiên giữa các phép toán trong biểu thức đó, từ đó nó sẽ tính toán hoặc trái-sang-phải hoặc phải-qua-trái. Ở đây chúng ta chỉ có một phép toán mà thôi, phép cộng: +
. Với phép cộng, tính toán sẽ là từ trái-qua-phải.
Giá trị 3 + 4
được tính toán trước. Kết quả là 7
.
7 + '5'
sẽ ra kết quả là "75"
bởi xuất hiện ép kiểu tại đây. JavaScript sẽ convert 7
sang dạng string, bạn có thể xem thêm tại câu hỏi 15. Và sau đó 2 string sẽ được nối lại với nhau bởi phép toán cộng +
. Kết quả "7" + "5"
sẽ là "75"
.
const num = parseInt("7*6", 10);
- A:
42
- B:
"42"
- C:
7
- D:
NaN
Đáp án
Chỉ có số đầu tiên trong chuỗi kí tự được trả về. Hệ cơ số là hệ thập phân (đối số thứ 2 trong hàm chính là cơ số: hệ thập phân, hệ 16, hệ 8, hệ nhị phân, vv.), Hàm parseInt
sẽ kiểm tra xem các ký tự trong chuỗi có đảm bảo hợp lệ hay không. Một khi chúng tìm ra ký tự không phải là ký tự hợp lệ trong hệ cơ số, nó dừng lại và bỏ qua các ký tự phía sau.
*
không phải là một số. Vậy nên nó sẽ chỉ convert ký tự "7"
sang hệ thập phân là 7
. num
sẽ có giá trị là 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 ]
Đáp án
Khi ta tiến hành map
một mảng, giá trị của num
sẽ chính là giá trị của phần tử hiện giờ trong vòng lặp. Trong trường hợp này, các phần tử đều là dạng số, tức là typeof num === "number"
sẽ là true
. Hàm map
sẽ tạo ra một mảng mởi từ các giá trị của mảng ban đầu.
Tuy nhiên chúng ta không hề trả về giá trị nào cả. Khi đó, hàm số sẽ mặc định trả về undefined
. Do đó qua mỗi vòng lặp, ta lại nhận được thêm một giá trị undefined
nữa.
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"
Đáp án
Đối số
sẽ được đưa vào hàm dạng tham trị, trừ phi nó là object, khi đó nó sẽ được đưa vào hàm dạng tham chiếu. birthYear
là dạng giá trị, vì nó là string chứ không phải object. Khi chúng ta đưa vào dạng giá trị, một bản sao của giá trị đó sẽ được tạo ra (xem thêm câu 46).
birthYear
trỏ đến giá trị là "1997"
. Đối số year
cũng sẽ rỏ đến giá trị "1997"
, nhưng giá trị này chỉ là một bản sao của giá trị mà birthYear
trỏ tới mà thôi, hai giá trị đó hoàn toàn khác nhau. Do đó khi ta thay đổi giá trị year
bằng "1998"
, chúng ta chỉ thay đổi giá trị của year
mà thôi. birthYear
sẽ vẫn giữ giá trị là "1997"
.
person
là một object. Biến member
có một tham chiếu tới cùng object mà person
trỏ tới. Khi chúng ta thay đổi một thuộc tính của object mà member
trỏ tới, giá trị của person
cũng sẽ tự động thay đổi theo, vì chúng có chung tham chiếu. name
của person
khi này sẽ có giá trị mới là "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!
Đáp án
Với lệnh throw
, chúng ta có thể tạo ra các errors tùy ý. Với câu lệnh đó, chúng ta có thể throw các exception. Một exeption có thể là một chuỗi, một số, một boolean hoặc một object. Trong trường hợp này thì nó là chuỗi 'Hello world'
.
Với lệnh catch
chúng ta có thể xử lý những exeption được throw ra khi thực hiện try
. Một exeption đã được throw ra: chuỗi 'Hello world'
. e
chính là chuỗi đó và chúng ta sẽ in ra. Kết quả là '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
Đáp án
Khi chúng ta trả về một thuộc tính, giá trị của thuộc tính bằng với giá trị đã được trả về bởi lệnh return, chứ không phải giá trị được set trong constructor. Chúng ta trả về giá trị là "Maserati"
, do đó myCar.make
sẽ là "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"
Đáp án
let x = y = 10;
chính là cách viết ngắn gọn của:
y = 10;
let x = y;
Khi ta set y
bằng 10
, thực tế chúng ta đã sử dụng biến global y
(window
nếu là trên browser, global
nếu là môi trường Node).Trên browser, window.y
sẽ là 10
.
Sau đó, chúng ta khai báo giá trị của x
với giá trị của y
, tức 10
. Tuy nhiên khi ta khai báo với từ khóa let
biến x sẽ chỉ tồn tại trong block scoped; hay trong trường hợp này là hàm thực hiện ngay lập tức
(immediately-invoked function - IIFE). Khi ta sử dụng phép toán typeof
, x
hoàn toàn chưa được định nghĩa: vì x
lúc này nằm bên ngoài block nó được định nghĩa lúc trước. Nghĩa là x
là undefined
. Do đó console.log(typeof x)
trả về "undefined"
.
Tuy nhiên với y
thì khác, ta đã có giá trị của y
khi set y
bằng 10
. Giá trị đó có thể truy cập được từ bất kì đâu bởi chúng là biến global. y
được định nghĩa với kiểu là "number"
. Do đó console.log(typeof y)
trả về "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
Đáp án
Chúng ta có thể xóa các thuộc tính khỏe object bằng từ khóa delete
, kể cả với prototype. Khi chúng ta xóa một thuộc tính trên prototype, nó sẽ bị vô hiệu hóa hoàn toàn trong chuỗi prototype. Trong trường hợp này, hàm bark
sẽ bị vô hiệu hóa ngay sau khi chúng ta thực hiện hàm xóa delete Dog.prototype.bark
, tất nhiên ta vẫn có thể truy cập vào nó nhưng giá trị sẽ là undefined
.
Khi chúng ta chạy một thứ không phải là hàm
, nó sẽ bắn ra một TypeError
. Trong trường hợp này là TypeError: pet.bark is not a function
, vì bản thân thuộc tính pet.bark
là 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}
Đáp án
Set
là một tập hơp các giá trị không trùng nhau.
Chúng ta đưa đầu vào là một mảng [1, 1, 2, 3, 4]
với giá trị 1
bị trùng. Giá trị trùng đó sẽ bị loại bỏ. Kết quả là {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
Đáp án
Một module khi được import sẽ là read-only: chúng ta sẽ không thể chỉnh sửa module đó, chỉ có bản thân module đó có thể chỉnh sửa giá trị của nó mà thôi.
Khi ta thay đổi giá trị cuả myCounter
, nó sẽ throw ra một lỗi: myCounter
là read-only và không thể thay đổi.
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
Đáp án
Phép toán delete
sẽ trả về một giá trị boolean: true
nếu xóa thành công, false
nếu thất bại. Tuy nhiên, nếu biến được khai báo với các từ khóa var
, const
hay let
thì nó sẽ không thể bị xóa bởi phép toán delete
.
Biến name
được khai báo với từ khóa const
, nên nó sẽ không thể bị xóa và trả về false
. Khi ta set age
bằng 21
, thực tế là ta đang sử dụng biến global age
. Ta có thể xóa sử dụng phép toán delete
, khi này delete age
trả về 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]
Đáp án
Chúng ta có thể unpack các giá trị từ mảng hoặc thuộc tính từ objects bằng phương pháp destructuring
. Ví dụ:
[a, b] = [1, 2];
Giá trị của a
sẽ là 1
, b
sẽ là 2
. Thực tế, câu hỏi của chúng ta đơn giản là:
[y] = [1, 2, 3, 4, 5];
Có nghĩa là y
chính là giá trị đầu tiên trong mảng, tức số 1
. Do đó khi ta in ra y
thì sẽ là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 }
Đáp án
Ta có thể kết hợp 2 object sử dụng phép toán spread operator
...
. Nó cho phép ta tạo ra bản sao của từng cặp key/values trong từng object và nối chúng lại với nhau thành một object mới. Trong trường hợp này chúng ta tạo ra các bản sao của các cặp key/value của object user
object, và nối chúng vào object admin
. admin
object khi này sẽ trở thành { 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"]
Đáp án
Với phương thức defineProperty
, chúng ta có thể thêm các thuộc tính mới, cũng như sửa các thuộc tính sẵn có của object. Khi chúng ta thêm thuộc tính vào object bằng defineProperty
, chúng sẽ mặc định là thuộc tính not enumerable. Phương thức Object.keys
sẽ trả về tất cả các thuộc tính enumerable của object, trong trường hợp này thì chỉ có "name"
mà thôi.
Thêm nữa, các thuộc tính được thêm bởi defineProperty
là mặc định không thể thay đổi được. Tất nhiên ta có thể override các điều đó bằng các thuộc tính như writable
, configurable
và enumerable
. Tức là defineProperty
là một cách rất mềm dẻo để tạo ra và điều chỉnh thuộc tính của object.
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}"
Đáp án
Đối số thứ hai của JSON.stringify
là replacer. Replacer Có thể là một hàm hoặc một mảng, nó sẽ quy định xem giá trị nào sẽ được chuỗi hóa ra sao.
Nếu replacer là một mảng, chỉ có các thuộc tính có tên trong mảng được convert thành chuỗi JSON. Trong trường hợp này, chỉ có các thuộc tính "level"
và "health"
được đưa vào, "username"
bị loại bỏ. data
giờ sẽ là "{"level":19, "health":90}"
.
Nếu replacer là function, hàm này sẽ được gọi trên từng thuộc tính của object được chuỗi hóa. Giá trị trả về sẽ là giá trị được đưa vào chuỗi JSON. Nếu trả về undefined
, thuộc tính này sẽ bị loại bỏ khỏi chuỗi.
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
Đáp án
Phép toán ++
sẽ trả về trước giá trị của toán hạng, sau đó tăng giá trị của toán hạng lên. Giá trị của num1
là 10
, vì increaseNumber
sẽ trả về giá trị của num
, đang là 10
, và sau đó mới tăng giá trị của num
lên.
num2
cũng là 10
, vì chúng ta đưa num1
vào increasePassedNumber
. number
bằng 10
(tức giá trị của num1
). Cũng giống như trên, phép toán ++
sẽ trả về trước giá trị của toán hạng, sau đó tăng giá trị của toán hạng lên. Giá trị của number
là 10
, do đó num2
cũng sẽ là 10
.