+11

JavaScript Nâng Cao - Kỳ 29

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à giá trị mặc định trong hàm

Output của đoạn code bên dưới là gì?

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"]
Đá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. Phân tích vấn đề

Để hiểu được kết quả này, chúng ta cần phân tích từng lời gọi hàm addHobby:

  1. addHobby("running", []):

    • Ở đây, chúng ta truyền một mảng rỗng [] làm đối số thứ hai.
    • Hàm sẽ thêm "running" vào mảng rỗng này và trả về ["running"].
    • Tuy nhiên, điều này không ảnh hưởng đến person.hobbies.
  2. addHobby("dancing"):

    • Ở đây, chúng ta không truyền đối số thứ hai.
    • Do đó, hàm sẽ sử dụng giá trị mặc định person.hobbies.
    • "dancing" được thêm vào person.hobbies.
  3. addHobby("baking", person.hobbies):

    • Ở đây, chúng ta trực tiếp truyền person.hobbies làm đối số thứ hai.
    • "baking" được thêm vào person.hobbies.

1.2. Giải thích chi tiết

Điểm quan trọng cần lưu ý là khi chúng ta sử dụng person.hobbies làm giá trị mặc định cho tham số hobbies, chúng ta đang tạo một tham chiếu đến mảng gốc trong object person.

Khi gọi addHobby("dancing"), do không có đối số thứ hai, hàm sử dụng giá trị mặc định person.hobbies. Điều này có nghĩa là "dancing" được thêm trực tiếp vào mảng hobbies của person.

Tương tự, khi gọi addHobby("baking", person.hobbies), chúng ta lại trực tiếp thao tác trên mảng hobbies của person.

1.3. Kết luận

Vì vậy, sau tất cả các lời gọi hàm, person.hobbies sẽ chứa ["coding", "dancing", "baking"]. Lời gọi đầu tiên với mảng rỗng không ảnh hưởng đến person.hobbies, trong khi hai lời gọi sau đó đều thêm phần tử vào mảng gốc.

Đây là một ví dụ điển hình về cách mà tham chiếu (reference) hoạt động trong JavaScript và cách mà giá trị mặc định trong hàm có thể ảnh hưởng đến kết quả.

2. Kế thừa và Constructor trong Class

Output của đoạn code bên dưới là gì?

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
Đáp án của câu hỏi này là 
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: B

Cùng mình đi tìm hiểu tại sao kết quả lại là như vậy nhé ❓️

2.1. Phân tích vấn đề

Để hiểu được kết quả này, chúng ta cần phân tích cách mà kế thừa và constructor hoạt động trong JavaScript:

  1. Chúng ta có hai class: Bird (lớp cha) và Flamingo (lớp con kế thừa từ Bird).
  2. Mỗi class đều có constructor riêng.
  3. Trong constructor của Flamingo, chúng ta gọi super().

2.2. Giải thích chi tiết

Khi chúng ta tạo một instance mới của Flamingo bằng cách gọi new Flamingo(), điều gì xảy ra?

  1. Constructor của Flamingo được gọi đầu tiên.
  2. Dòng console.log("I'm pink. 🌸"); trong constructor của Flamingo được thực thi.
  3. Sau đó, super() được gọi. super() ở đây có nghĩa là gọi constructor của lớp cha (Bird).
  4. Constructor của Bird được thực thi, in ra "I'm a bird. 🦢".

2.3. Vai trò của super()

super() là một phương thức đặc biệt được sử dụng để gọi constructor của lớp cha. Trong JavaScript, khi một class kế thừa từ một class khác, nó phải gọi super() trong constructor của nó trước khi sử dụng this.

Nếu chúng ta không gọi super() trong constructor của lớp con, JavaScript sẽ throw một ReferenceError.

2.4. Kết luận

Vì vậy, khi chúng ta tạo một instance mới của Flamingo, chúng ta sẽ thấy hai dòng output:

  1. "I'm pink. 🌸" (từ constructor của Flamingo)
  2. "I'm a bird. 🦢" (từ constructor của Bird, được gọi thông qua super())

3. Thao tác với mảng và hằng số

Câu lệnh nào sẽ bị lỗi?

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
Đáp án của câu hỏi này là 
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: D

Cùng mình đi tìm hiểu tại sao kết quả lại là như vậy nhé ❓️

3.1. Phân tích vấn đề

Để hiểu được kết quả này, chúng ta cần phân tích từng câu lệnh và hiểu về cách const hoạt động với mảng trong JavaScript:

  1. emojis.push("🦌"): Thêm một phần tử vào cuối mảng.
  2. emojis.splice(0, 2): Xóa 2 phần tử đầu tiên của mảng.
  3. emojis = [...emojis, "🥂"]: Cố gắng gán lại giá trị cho emojis.
  4. emojis.length = 0: Đặt độ dài của mảng về 0, xóa tất cả phần tử.

3.2. Giải thích chi tiết

Khi chúng ta khai báo một mảng với const, điều đó có nghĩa là chúng ta không thể gán lại biến đó cho một giá trị mới. Tuy nhiên, nó không ngăn chúng ta thay đổi nội dung của mảng.

  1. Câu lệnh 1 và 2 hoàn toàn hợp lệ vì chúng chỉ thay đổi nội dung của mảng, không gán lại biến emojis.

  2. Câu lệnh 3 sẽ gây ra lỗi vì nó cố gắng gán lại giá trị cho emojis. Mặc dù chúng ta đang sử dụng spread operator ... để tạo một mảng mới, nhưng việc gán lại giá trị cho một biến const là không được phép.

  3. Câu lệnh 4 là hợp lệ vì nó chỉ thay đổi thuộc tính length của mảng, không gán lại giá trị cho biến emojis.

3.3. Hiểu về const với mảng và object

Điều quan trọng cần nhớ là const chỉ ngăn chặn việc gán lại biến, không ngăn chặn việc thay đổi nội dung của mảng hoặc object. Đây là một điểm gây nhầm lẫn phổ biến trong JavaScript.

Với mảng và object, const đảm bảo rằng biến luôn trỏ đến cùng một vùng nhớ, nhưng không đảm bảo rằng nội dung của vùng nhớ đó không thay đổi.

3.4. Kết luận

Vì vậy, chỉ có câu lệnh 3 sẽ gây ra lỗi. Các câu lệnh khác, mặc dù thay đổi nội dung của mảng, vẫn là hợp lệ khi làm việc với một mảng được khai báo bằng const.

4. Iterator và Symbol.iterator

Ta cần thêm gì vào object person để khi gọi [...person] sẽ cho kết quả là ["Lydia Hallie", 21]?

const person = {
  name: "Lydia Hallie",
  age: 21
}

[...person] // ["Lydia Hallie", 21]
  • A: Nothing, object are iterable by default
  • 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 }
Đá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é ❓️

4.1. Phân tích vấn đề

Để hiểu được kết quả này, chúng ta cần biết về:

  1. Cách spread operator ... hoạt động
  2. Khái niệm về iterable trong JavaScript
  3. Cách sử dụng Symbol.iterator để tạo một iterable object trong JavaScript

4.2. Giải thích chi tiết

  1. Spread operator và iterable:

    • Spread operator ... hoạt động với các đối tượng có thể lặp (iterable).
    • Mặc định, object trong JavaScript không phải là iterable.
  2. Symbol.iterator:

    • Để làm cho một object trở thành iterable, chúng ta cần định nghĩa phương thức Symbol.iterator.
    • Phương thức này phải trả về một iterator object, thường là một generator.
  3. Generator function:

    • Generator function được định nghĩa bằng cách sử dụng dấu *.
    • Nó cho phép chúng ta sử dụng từ khóa yield để trả về các giá trị tuần tự.
  4. Phân tích các lựa chọn:

    • A: Không đúng vì object không phải iterable mặc định.
    • B: Gần đúng, nhưng yield* this[x] sẽ không hoạt động như mong muốn.
    • C: Đúng, yield* Object.values(this) sẽ trả về tất cả giá trị của object.
    • D: Không đúng, yield this sẽ trả về toàn bộ object thay vì các giá trị riêng lẻ.

4.3. Giải pháp chi tiết

Đáp án C là chính xác:

const person = {
  name: "Lydia Hallie",
  age: 21,
  *[Symbol.iterator]() {
    yield* Object.values(this);
  }
};
  • *[Symbol.iterator](): Định nghĩa một generator function làm phương thức iterator.
  • yield* Object.values(this): Sử dụng yield* để yield tất cả giá trị từ Object.values(this).
  • Object.values(this) trả về một mảng chứa tất cả giá trị của object.

Khi sử dụng [...person], JavaScript sẽ gọi phương thức Symbol.iterator, và generator sẽ yield lần lượt các giá trị "Lydia Hallie" và 21.

5. Truthy và Falsy trong JavaScript

Output là gì?

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
Đá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é ❓️

5.1. Phân tích vấn đề

Để hiểu được kết quả này, chúng ta cần xem xét:

  1. Cách hoạt động của phương thức forEach
  2. Khái niệm về truthy và falsy trong JavaScript
  3. Cách điều kiện if đánh giá các giá trị

5.2. Giải thích chi tiết

  1. Phương thức forEach:

    • forEach lặp qua từng phần tử trong mảng nums.
    • Với mỗi phần tử, nó gọi hàm callback được cung cấp.
  2. Truthy và Falsy:

    • Trong JavaScript, mọi giá trị đều có tính chất boolean ngầm định.
    • Giá trị 0 là falsy, mọi số khác 0 đều là truthy.
  3. Điều kiện if:

    • if (num) sẽ đánh giá num như một giá trị boolean.
    • Nếu num là truthy, khối lệnh bên trong if sẽ được thực thi.
  4. Phân tích từng lần lặp:

    • Với 0: if(0) là false, không tăng count
    • Với 1: if(1) là true, count tăng lên 1
    • Với 2: if(2) là true, count tăng lên 2
    • Với 3: if(3) là true, count tăng lên 3

5.3. Kết luận

Sau khi lặp qua tất cả các phần tử, count sẽ có giá trị là 3. Điều này là vì có 3 số trong mảng nums (1, 2, và 3) được đánh giá là truthy.

Một số điểm cần nhớ:

  • Số 0 là falsy
  • Chuỗi rỗng "" là falsy
  • null và undefined đều là falsy
  • Mọi số khác 0 (kể cả số âm) đều là truthy
  • Mọi chuỗi không rỗng đều là truthy
  • Tất cả các object và array (kể cả rỗng) đều là truthy

Hiểu rõ về các khái niệm này sẽ giúp bạn viết code JavaScript hiệu quả và tránh được nhiều lỗi tiềm ẩn.


Đó là tất cả cho JavaScript Nâng Cao - Kỳ 29. Hy vọng qua bài viết này, các bạn đã hiểu rõ hơn về cách hoạt động của tham chiếu và giá trị mặc định trong hàm, kế thừa và constructor trong class, thao tác với mảng và hằng số, iterator và Symbol.iterator, cũng như truthy và falsy trong JavaScript.

Nếu có bất kỳ câu hỏi hoặc thắc mắc nào, đừng ngại comment bên dưới nhé. 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í