JavaScript Nâng Cao - Kỳ 24
Có một câu nói vui là: Trên đời chỉ có thứ nhiều người chửi và thứ không ai thèm dùng.
Javascript là một ví dụ điển hình, nó có một số điểm thú vị nhưng cũng khiến chúng ta phải đau đầu. Lý thuyết thì dễ hiểu, nhưng khi thực hành là cả một vấn đề. Vậy nên, mình sẽ cùng các bạn đi sâu vào từng ví dụ cụ thể và phân tích, mổ xẻ nó để hiểu hơn về Javascript nhé
Series này có thể sẽ khá dài mình không biết sẽ có bao nhiêu Kỳ tuy nhiên để tiện cho các bạn nào không đọc các bài trước đó của mình về JS thì trong loạt bài này mình sẽ giải thích lại toàn bộ. Các lý thuyết trong loạt bài này mình cũng có thể sẽ giải thích lại nhiều lần (tùy hứng) để các bạn có thể nắm rõ nó hơn nhé.
Ok vào bài thôi nào... GÉT GÔ 🚀
Nếu có bất kỳ câu hỏi nào đừng ngại hãy bình luận dưới phần
comment
nhé. Hoặc chỉ cần để lại mộtcomment chào mình
là đã giúp mình có thêm động lực hoàn thành series này. Cảm ơn các bạn rất nhiều. 🤗
1. Tham chiếu và Spread Operator
Output của đoạn code bên dưới là gì?
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}
Đáp án của câu hỏi này là
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: C
Cùng mình đi tìm hiểu tại sao kết quả lại là như vậy nhé ❓️
1.1. Spread Operator và Object Copying
Trước tiên, chúng ta cần hiểu về Spread Operator (...
) trong JavaScript. Spread Operator cho phép chúng ta copy tất cả các thuộc tính của một object vào một object mới.
Trong đoạn code trên, cả hai hàm changeAge
và changeAgeAndName
đều có tham số mặc định là một bản copy của object person
thông qua Spread Operator: { ...person }
.
1.2. Phân tích từng bước
-
Đầu tiên, chúng ta gọi
changeAge(person)
:- Hàm này nhận vào chính object
person
(không phải bản copy). - Nó tăng
age
lên 1. - Kết quả:
person
trở thành{ name: "Lydia", age: 22 }
.
- Hàm này nhận vào chính object
-
Tiếp theo, chúng ta gọi
changeAgeAndName()
:- Vì không truyền tham số, nên hàm sử dụng giá trị mặc định là một bản copy của
person
. - Các thay đổi (tăng
age
và đổiname
) chỉ ảnh hưởng đến bản copy này, không ảnh hưởng đếnperson
gốc.
- Vì không truyền tham số, nên hàm sử dụng giá trị mặc định là một bản copy của
-
Cuối cùng, khi chúng ta log
person
, nó vẫn giữ giá trị sau bước 1:{ name: "Lydia", age: 22 }
.
1.3. Lưu ý quan trọng
Điều quan trọng cần nhớ là khi chúng ta truyền một object vào hàm, chúng ta đang truyền tham chiếu của object đó. Nhưng khi sử dụng Spread Operator để tạo một bản copy, chúng ta đang tạo ra một object mới hoàn toàn, và các thay đổi trên object mới này không ảnh hưởng đến object gốc.
Đây là một ví dụ rất hay để hiểu về cách JavaScript xử lý tham chiếu và giá trị khi làm việc với objects.
2. Spread Operator và Function Parameters
Phép tính nào dưới đây trả về 6
?
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])
Đáp án của câu hỏi này là
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: C
Hãy cùng mình phân tích từng đáp án để hiểu rõ hơn về cách Spread Operator hoạt động nhé!
2.1. Phân tích các đáp án
A. sumValues([...1, 2, 3])
:
- Đây là cú pháp không hợp lệ. Ta không thể sử dụng Spread Operator với một số.
B. sumValues([...[1, 2, 3]])
:
- Điều này tạo ra một mảng mới
[1, 2, 3]
và đặt nó trong một mảng khác. - Kết quả là
sumValues
nhận vào một mảng duy nhất làm đối số, không phải 3 số riêng biệt.
C. sumValues(...[1, 2, 3])
:
- Đây là cách sử dụng đúng của Spread Operator.
- Nó "mở rộng" mảng thành các phần tử riêng lẻ, tương đương với
sumValues(1, 2, 3)
.
D. sumValues([1, 2, 3])
:
- Điều này truyền một mảng duy nhất vào hàm, không phải 3 số riêng biệt.
2.2. Giải thích chi tiết đáp án đúng
Đáp án C là chính xác vì:
-
Spread Operator
...
khi được sử dụng với một mảng sẽ "mở rộng" mảng đó thành các phần tử riêng lẻ. -
Trong trường hợp này,
...[1, 2, 3]
tương đương với việc viết1, 2, 3
. -
Do đó,
sumValues(...[1, 2, 3])
thực chất làsumValues(1, 2, 3)
. -
Hàm
sumValues
nhận 3 tham sốx
,y
, vàz
, và trả về tổng của chúng. -
Kết quả là 1 + 2 + 3 = 6.
2.3. Ứng dụng thực tế
Spread Operator rất hữu ích trong nhiều tình huống, ví dụ:
- Kết hợp các mảng:
const newArray = [...array1, ...array2]
- Tạo bản sao của mảng:
const copy = [...originalArray]
- Chuyển đổi một chuỗi thành mảng các ký tự:
const chars = [...'Hello']
Hiểu rõ cách sử dụng Spread Operator sẽ giúp code của bạn ngắn gọn và dễ đọc hơn rất nhiều!
3. Toán tử gán kết hợp và Array Indexing
Output là gì?
let num = 1;
const list = ["🥳", "🤠", "🥰", "🤪"];
console.log(list[(num += 1)]);
- A:
🤠
- B:
🥰
- C:
SyntaxError
- D:
ReferenceError
Đáp án của câu hỏi này là
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: B
Hãy cùng mình phân tích từng bước để hiểu rõ hơn về cách JavaScript xử lý biểu thức này nhé!
3.1. Phân tích từng bước
-
Ban đầu,
num
có giá trị là 1. -
Trong biểu thức
(num += 1)
:- Toán tử
+=
là toán tử gán kết hợp, nó thực hiện phép cộng và gán kết quả ngay lập tức. num += 1
tương đương vớinum = num + 1
.- Sau phép toán này,
num
có giá trị là 2.
- Toán tử
-
Biểu thức
list[(num += 1)]
trở thànhlist
. -
Trong mảng
list
, phần tử ở index 2 (phần tử thứ 3) là"🥰"
.
3.2. Lưu ý quan trọng
-
Toán tử
+=
không chỉ thực hiện phép cộng mà còn gán giá trị mới cho biến. Đây là một điểm quan trọng cần nhớ khi làm việc với JavaScript. -
Indexing trong mảng JavaScript bắt đầu từ 0. Vì vậy,
list
là phần tử thứ 3 trong mảng. -
JavaScript cho phép sử dụng biểu thức làm index của mảng. Điều này rất hữu ích trong nhiều tình huống lập trình.
3.3. Ví dụ mở rộng
Để hiểu rõ hơn, hãy xem xét một vài ví dụ khác:
let i = 0;
const arr = ['a', 'b', 'c', 'd'];
console.log(arr[i]); // 'a'
console.log(arr[i += 1]); // 'b'
console.log(arr[++i]); // 'c'
console.log(i); // 2
Trong ví dụ này, chúng ta thấy sự khác biệt giữa i += 1
(tăng i lên 1 và sử dụng giá trị mới) và ++i
(tăng i lên 1 trước khi sử dụng).
Hiểu rõ cách hoạt động của các toán tử và indexing trong JavaScript sẽ giúp bạn viết code hiệu quả và tránh được nhiều lỗi không đáng có!
4. Optional Chaining trong JavaScript
Output là gì?
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
Đáp án của câu hỏi này là
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: B
Hãy cùng mình đi sâu vào từng dòng code để hiểu rõ cách Optional Chaining (?.
) hoạt động trong JavaScript nhé!
4.1. Optional Chaining là gì?
Optional Chaining (?.
) là một tính năng mới trong JavaScript, cho phép chúng ta đọc giá trị của một thuộc tính nằm sâu trong chuỗi các object mà không cần kiểm tra xem mỗi tham chiếu trong chuỗi có tồn tại hay không.
4.2. Phân tích từng dòng
-
console.log(person.pet?.name);
person.pet
tồn tại, nênperson.pet.name
được truy cập.- Kết quả:
"Mara"
-
console.log(person.pet?.family?.name);
person.pet
tồn tại, nhưngperson.pet.family
không tồn tại.- Optional Chaining dừng lại ở
family
và trả vềundefined
. - Kết quả:
undefined
-
console.log(person.getFullName?.());
person.getFullName
tồn tại và là một hàm.- Hàm được gọi và trả về kết quả.
- Kết quả:
"Lydia Hallie"
-
console.log(member.getLastName?.());
member
không tồn tại.- Optional Chaining dừng lại ngay lập tức và trả về
undefined
. - Kết quả:
undefined
4.3. Lưu ý quan trọng
- Optional Chaining giúp tránh lỗi khi truy cập thuộc tính của
undefined
hoặcnull
. - Nó rất hữu ích khi làm việc với dữ liệu có cấu trúc phức tạp hoặc dữ liệu từ API.
- Optional Chaining có thể được sử dụng với các phương thức và các phần tử của mảng.
4.4. Ví dụ mở rộng
Để hiểu rõ hơn về Optional Chaining, hãy xem xét một vài ví dụ sau:
const user = {
address: {
street: "123 JS Street",
city: "JS City"
}
};
console.log(user.address?.zipCode); // undefined
console.log(user.phoneNumbers?.[0]); // undefined
const admin = null;
console.log(admin?.name); // undefined
function printName(obj) {
console.log(obj?.name ?? "Anonymous");
}
printName({name: "Alice"}); // Alice
printName({}); // Anonymous
printName(null); // Anonymous
Trong các ví dụ trên, chúng ta thấy Optional Chaining có thể được sử dụng với:
- Thuộc tính của object (
user.address?.zipCode
) - Phần tử của mảng (
user.phoneNumbers?.
) - Thuộc tính của
null
hoặcundefined
(admin?.name
)
Kết hợp với toán tử Nullish Coalescing (??
), chúng ta có thể cung cấp giá trị mặc định khi thuộc tính không tồn tại.
Hiểu và sử dụng đúng Optional Chaining sẽ giúp code của bạn an toàn hơn và tránh được nhiều lỗi runtime không mong muốn!
5. Điều kiện với indexOf
Output là gì?
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
Đáp án của câu hỏi này là
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: B
Hãy cùng mình đi sâu vào phân tích đoạn code này để hiểu rõ hơn về cách indexOf
hoạt động và cách JavaScript xử lý điều kiện!
5.1. Phương thức indexOf
Trước tiên, chúng ta cần hiểu về phương thức indexOf
:
indexOf
trả về chỉ số (index) đầu tiên của phần tử được tìm thấy trong mảng.- Nếu phần tử không tồn tại trong mảng,
indexOf
sẽ trả về-1
. - Quan trọng: Nếu phần tử được tìm thấy ở vị trí đầu tiên của mảng,
indexOf
sẽ trả về0
.
5.2. Phân tích đoạn code
-
groceries.indexOf("banana")
trả về0
vì "banana" nằm ở vị trí đầu tiên trong mảng. -
Trong câu lệnh
if
, JavaScript sẽ chuyển đổi kết quả củaindexOf
thành giá trị boolean:0
được coi là giá trị falsy trong JavaScript.- Do đó, điều kiện
if (groceries.indexOf("banana"))
sẽ được đánh giá làfalse
.
-
Vì điều kiện là
false
, khối code trongelse
sẽ được thực thi. -
Kết quả:
"We don't have to buy bananas!"
được in ra console.
5.3. Lưu ý quan trọng
Đây là một ví dụ tuyệt vời về cách mà các giá trị được chuyển đổi thành boolean trong JavaScript có thể gây ra những kết quả không mong muốn. Một số điểm cần lưu ý:
- Các giá trị falsy trong JavaScript bao gồm:
false
,0
,""
(chuỗi rỗng),null
,undefined
, vàNaN
. - Khi sử dụng
indexOf
trong câu lệnh điều kiện, cần cẩn thận với trường hợp phần tử nằm ở vị trí đầu tiên của mảng.
5.4. Cách viết an toàn hơn
Để tránh nhầm lẫn, chúng ta có thể viết lại đoạn code trên như sau:
if (groceries.indexOf("banana") !== -1) {
console.log("We have bananas!");
} else {
console.log("We don't have bananas!");
}
Hoặc sử dụng phương thức includes
(ES6+):
if (groceries.includes("banana")) {
console.log("We have bananas!");
} else {
console.log("We don't have bananas!");
}
Cả hai cách trên đều sẽ cho kết quả chính xác, không phụ thuộc vào vị trí của phần tử trong mảng.
Hiểu rõ cách JavaScript xử lý các giá trị trong câu lệnh điều kiện sẽ giúp bạn tránh được nhiều lỗi logic không mong muốn trong code của mình!
Kết luận
Qua 5 ví dụ trên, chúng ta đã đi sâu vào một số khía cạnh thú vị của JavaScript:
- Cách Spread Operator hoạt động với objects và tham chiếu.
- Sử dụng Spread Operator với tham số hàm.
- Toán tử gán kết hợp và indexing trong mảng.
- Optional Chaining và cách nó giúp code an toàn hơn.
- Cách
indexOf
hoạt động và những điều cần lưu ý khi sử dụng nó trong câu lệnh điều kiện.
Mỗi ví dụ đều cho thấy những nét đặc trưng của JavaScript, từ cách xử lý objects và mảng cho đến cách ngôn ngữ này đánh giá các biểu thức trong câu lệnh điều kiện.
Hy vọng qua bài viết này, các bạn đã có thêm những hiểu biết sâu sắc về JavaScript. Hãy nhớ rằng, để thực sự nắm vững những khái niệm này, việc thực hành và áp dụng chúng vào các dự án thực tế là rất quan trọng.
Hẹn gặp lại các bạn trong các bài viết tiếp theo của series "JavaScript Nâng Cao"!
Nếu có bất kỳ câu hỏi nào đừng ngại hãy bình luận dưới phần
comment
nhé. Hoặc chỉ cần để lại mộtcomment chào mình
là đã giúp mình có thêm động lực hoàn thành series này. Cảm ơn các bạn rất nhiều. 🤗
All rights reserved