+11

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ột comment 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 changeAgechangeAgeAndName đề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

  1. Đầ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 }.
  2. 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à đổi name) chỉ ảnh hưởng đến bản copy này, không ảnh hưởng đến person gốc.
  3. 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ì:

  1. 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ẻ.

  2. Trong trường hợp này, ...[1, 2, 3] tương đương với việc viết 1, 2, 3.

  3. Do đó, sumValues(...[1, 2, 3]) thực chất là sumValues(1, 2, 3).

  4. Hàm sumValues nhận 3 tham số x, y, và z, và trả về tổng của chúng.

  5. 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

  1. Ban đầu, num có giá trị là 1.

  2. 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ới num = num + 1.
    • Sau phép toán này, num có giá trị là 2.
  3. Biểu thức list[(num += 1)] trở thành list.

  4. 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

  1. console.log(person.pet?.name);

    • person.pet tồn tại, nên person.pet.name được truy cập.
    • Kết quả: "Mara"
  2. console.log(person.pet?.family?.name);

    • person.pet tồn tại, nhưng person.pet.family không tồn tại.
    • Optional Chaining dừng lại ở family và trả về undefined.
    • Kết quả: undefined
  3. 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"
  4. 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ặc null.
  • 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ặc undefined (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

  1. groceries.indexOf("banana") trả về 0 vì "banana" nằm ở vị trí đầu tiên trong mảng.

  2. Trong câu lệnh if, JavaScript sẽ chuyển đổi kết quả của indexOf 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.
  3. Vì điều kiện là false, khối code trong else sẽ được thực thi.

  4. 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:

  1. Cách Spread Operator hoạt động với objects và tham chiếu.
  2. Sử dụng Spread Operator với tham số hàm.
  3. Toán tử gán kết hợp và indexing trong mảng.
  4. Optional Chaining và cách nó giúp code an toàn hơn.
  5. 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ột comment 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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí