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
commentnhé. Hoặc chỉ cần để lại mộtcomment chào mìnhlà đã 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 agelên 1.
- Kết quả: persontrở 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 agevà đổiname) chỉ ảnh hưởng đến bản copy này, không ảnh hưởng đếnpersongố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à sumValuesnhậ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 sumValuesnhậ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, numcó 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 += 1tương đương với- num = num + 1.
- Sau phép toán này, numcó 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, listlà 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: undefinedundefinedundefinedundefined
- B: MaraundefinedLydia Hallieundefined
- C: MaranullLydia Hallienull
- D: nullReferenceErrornullReferenceError
Đá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.pettồn tại, nên- person.pet.nameđược truy cập.
- Kết quả: "Mara"
 
- 
console.log(person.pet?.family?.name);- person.pettồn tại, nhưng- person.pet.familykhông tồn tại.
- Optional Chaining dừng lại ở familyvà trả vềundefined.
- Kết quả: undefined
 
- 
console.log(person.getFullName?.());- person.getFullNametồ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?.());- memberkhô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 undefinedhoặ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 nullhoặ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:
- indexOftrả 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, indexOfsẽ 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, indexOfsẽ trả về0.
5.2. Phân tích đoạn code
- 
groceries.indexOf("banana")trả về0vì "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ủaindexOfthà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 trongelsesẽ đượ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 indexOftrong 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 indexOfhoạ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
commentnhé. Hoặc chỉ cần để lại mộtcomment chào mìnhlà đã 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
 
  
 