+12

JavaScript Nâng Cao - Kỳ 15

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. Generator Functions và yield

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

function* startGame() {
  const answer = yield "Do you love JavaScript?";
  if (answer !== "Yes") {
    return "Oh wow... Guess we're gone here";
  }
  return "JavaScript loves you back ❤️";
}

const game = startGame();
console.log(/* 1 */); // Do you love JavaScript?
console.log(/* 2 */); // JavaScript loves you back ❤️
  • A: game.next("Yes").valuegame.next().value
  • B: game.next.value("Yes")game.next.value()
  • C: game.next().valuegame.next("Yes").value
  • D: game.next.value()game.next.value("Yes")
Đá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. Generator Functions là gì?

Generator Functions là một loại hàm đặc biệt trong JavaScript, được định nghĩa bằng cách thêm dấu sao (*) sau từ khóa function. Chúng cho phép chúng ta tạm dừng và tiếp tục thực thi hàm tại nhiều điểm khác nhau.

1.2. Từ khóa yield

Từ khóa yield trong Generator Functions có vai trò quan trọng:

  1. Nó tạm dừng thực thi hàm.
  2. Nó trả về giá trị được chỉ định sau yield.
  3. Nó lưu trạng thái của hàm, cho phép tiếp tục từ điểm đó trong lần gọi tiếp theo.

1.3. Phân tích đoạn code

Hãy xem xét từng bước:

  1. const game = startGame(); khởi tạo generator.
  2. console.log(/* 1 */); - Ở đây, chúng ta cần gọi game.next().value. Điều này sẽ chạy generator đến yield đầu tiên và trả về "Do you love JavaScript?".
  3. console.log(/* 2 */); - Ở đây, chúng ta cần gọi game.next("Yes").value. Điều này sẽ:
    • Truyền "Yes" vào generator, gán nó cho answer.
    • Tiếp tục thực thi cho đến khi gặp return.
    • Trả về "JavaScript loves you back ❤️".

1.4. Tại sao không phải các đáp án khác?

  • Đáp án A sai vì thứ tự gọi next() không đúng.
  • Đáp án B và D sai vì next là một method, không phải một property có value.

1.5. Ví dụ minh họa

Để hiểu rõ hơn, hãy xem ví dụ sau:

function* countToThree() {
  yield 1;
  yield 2;
  return 3;
}

const counter = countToThree();
console.log(counter.next().value); // 1
console.log(counter.next().value); // 2
console.log(counter.next().value); // 3
console.log(counter.next().value); // undefined

Mỗi lần gọi next(), generator sẽ chạy đến yield tiếp theo và tạm dừng. Khi gặp return, generator sẽ kết thúc và không thể tiếp tục. Nếu gọi next() sau khi generator kết thúc, giá trị trả về sẽ là undefined.

2. String.raw và Chuỗi Template Literals

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

console.log(String.raw`Hello\nworld`);
  • A: Hello world!
  • B: Hello
         world
  • C: Hello\nworld
  • D: Hello\n
         world
Đá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é ❓️

2.1. String.raw là gì?

String.raw là một method của đối tượng String trong JavaScript. Nó được sử dụng như một tag function cho template literals, cho phép chúng ta truy cập các chuỗi nguyên bản mà không xử lý các ký tự escape.

2.2. Template Literals

Template literals là một cách để tạo chuỗi trong JavaScript, sử dụng backticks (`) thay vì dấu nháy đơn hoặc kép. Chúng cho phép chèn biểu thức và xuống dòng dễ dàng.

2.3. Phân tích đoạn code

Trong đoạn code trên:

  1. Hello\nworld là một template literal.
  2. Thông thường, \n sẽ được hiểu là ký tự xuống dòng.
  3. Tuy nhiên, String.raw ngăn chặn việc xử lý các ký tự escape như \n.

2.4. Tại sao không phải các đáp án khác?

  • Đáp án A và B sai vì chúng xử lý \n như một ký tự xuống dòng.
  • Đáp án D sai vì nó tách \n thành hai dòng, điều mà String.raw không làm.

2.5. Ví dụ minh họa

Để hiểu rõ hơn sự khác biệt, hãy xem ví dụ sau:

console.log(`Hello\nworld`);
// Output:
// Hello
// world

console.log(String.raw`Hello\nworld`);
// Output: Hello\nworld

String.raw rất hữu ích khi bạn muốn làm việc với các đường dẫn file hoặc các chuỗi chứa nhiều ký tự backslash mà không muốn chúng bị xử lý như các ký tự escape.

3. Async/Await và Promises

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

async function getData() {
  return await Promise.resolve("I made it!");
}

const data = getData();
console.log(data);
  • A: "I made it!"
  • B: Promise {<resolved>: "I made it!"}
  • C: Promise {<pending>}
  • D: undefined
Đá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é ❓️

3.1. Async/Await là gì?

Async/Await là một cú pháp trong JavaScript giúp làm việc với các thao tác bất đồng bộ dễ dàng hơn. Nó được xây dựng trên nền tảng của Promises.

  • async được sử dụng để khai báo một hàm bất đồng bộ.
  • await được sử dụng để đợi một Promise hoàn thành.

3.2. Promises

Promises là một đối tượng đại diện cho sự hoàn thành hoặc thất bại của một thao tác bất đồng bộ.

3.3. Phân tích đoạn code

  1. getData() là một async function, nên nó luôn trả về một Promise.
  2. await Promise.resolve("I made it!") sẽ đợi Promise hoàn thành, nhưng điều này xảy ra bên trong hàm async.
  3. const data = getData(); gọi hàm getData nhưng không đợi nó hoàn thành.
  4. console.log(data); in ra Promise đang ở trạng thái pending.

3.4. Tại sao không phải các đáp án khác?

  • Đáp án A sai vì chúng ta đang in ra Promise, không phải giá trị đã resolve.
  • Đáp án B sai vì tại thời điểm log, Promise vẫn đang pending.
  • Đáp án D sai vì async function luôn trả về Promise, không phải undefined.

3.5. Ví dụ minh họa

Để lấy giá trị thực từ Promise, chúng ta có thể sử dụng .then() hoặc một async function khác:

async function getData() {
  return await Promise.resolve("I made it!");
}

getData().then(result => console.log(result)); // "I made it!"

// Hoặc

async function logData() {
  const result = await getData();
  console.log(result); // "I made it!"
}

logData();

4. Array Methods và Return Values

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

function addToList(item, list) {
  return list.push(item);
}

const result = addToList("apple", ["banana"]);
console.log(result);
  • A: ['apple', 'banana']
  • B: 2
  • C: true
  • D: undefined
Đá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é ❓️

4.1. Array.prototype.push()

Phương thức push() trong JavaScript được sử dụng để thêm một hoặc nhiều phần tử vào cuối một mảng. Điều quan trọng cần nhớ là push() trả về độ dài mới của mảng sau khi thêm phần tử, không phải mảng đã được cập nhật.

4.2. Phân tích đoạn code

  1. Ban đầu, mảng ["banana"] có độ dài là 1.
  2. list.push(item) thêm "apple" vào mảng và trả về độ dài mới của mảng.
  3. Hàm addToList trả về kết quả của list.push(item), tức là độ dài mới của mảng.
  4. const result = addToList("apple", ["banana"]) gán giá trị trả về của addToList cho result.
  5. console.log(result) in ra 2, là độ dài mới của mảng.

4.3. Tại sao không phải các đáp án khác?

  • Đáp án A sai vì push() không trả về mảng đã cập nhật.
  • Đáp án C sai vì push() không trả về giá trị boolean.
  • Đáp án D sai vì push() luôn trả về một số (độ dài mới của mảng).

4.4. Ví dụ minh họa

Để hiểu rõ hơn về cách push() hoạt động, hãy xem ví dụ sau:

const fruits = ['banana'];
console.log(fruits.push('apple')); // 2
console.log(fruits); // ['banana', 'apple']

const vegetables = ['carrot'];
const newLength = vegetables.push('broccoli', 'cucumber');
console.log(newLength); // 3
console.log(vegetables); // ['carrot', 'broccoli', 'cucumber']

5. Object.freeze() và Immutability

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

const box = { x: 10, y: 20 };

Object.freeze(box);

const shape = box;
shape.x = 100;

console.log(shape);
  • A: { x: 100, y: 20 }
  • B: { x: 10, y: 20 }
  • C: { x: 100 }
  • D: ReferenceError
Đá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é ❓️

5.1. Object.freeze() là gì?

Object.freeze() là một phương thức trong JavaScript được sử dụng để "đóng băng" một đối tượng. Khi một đối tượng bị đóng băng:

  1. Không thể thêm thuộc tính mới.
  2. Không thể xóa hoặc thay đổi các thuộc tính hiện có.
  3. Không thể thay đổi tính có thể cấu hình (configurable), có thể liệt kê (enumerable), hoặc có thể ghi (writable) của các thuộc tính.

Tuy nhiên, cần lưu ý rằng Object.freeze() chỉ đóng băng nông (shallow freeze), nghĩa là nó không ảnh hưởng đến các đối tượng lồng nhau.

5.2. Phân tích đoạn code

  1. const box = { x: 10, y: 20 }; tạo một đối tượng box.
  2. Object.freeze(box); đóng băng đối tượng box.
  3. const shape = box; tạo một tham chiếu mới đến cùng một đối tượng đã bị đóng băng.
  4. shape.x = 100; cố gắng thay đổi giá trị của x, nhưng không thành công vì đối tượng đã bị đóng băng.
  5. console.log(shape); in ra đối tượng ban đầu không bị thay đổi.

5.3. Tại sao không phải các đáp án khác?

  • Đáp án A sai vì đối tượng đã bị đóng băng, không thể thay đổi giá trị của x.
  • Đáp án C sai vì không có thuộc tính nào bị xóa.
  • Đáp án D sai vì việc cố gắng thay đổi một đối tượng đã đóng băng không gây ra lỗi, nó chỉ đơn giản là không có tác dụng.

5.4. Ví dụ minh họa

Để hiểu rõ hơn về cách Object.freeze() hoạt động, hãy xem ví dụ sau:

const person = {
  name: "John",
  age: 30,
  address: {
    city: "New York"
  }
};

Object.freeze(person);

person.name = "Jane"; // Không có tác dụng
person.newProp = "Test"; // Không có tác dụng
delete person.age; // Không có tác dụng

person.address.city = "Los Angeles"; // Có tác dụng vì đây là đối tượng lồng nhau

console.log(person);
// Output: { name: "John", age: 30, address: { city: "Los Angeles" } }

Trong ví dụ này, bạn có thể thấy rằng các thuộc tính trực tiếp của person không thể bị thay đổi, nhưng thuộc tính của đối tượng lồng nhau (address) vẫn có thể bị thay đổi.

Kết luận

Qua bài viết này, chúng ta đã cùng nhau tìm hiểu về:

  1. Generator Functions và cách sử dụng yield.
  2. String.raw và cách nó xử lý các ký tự escape trong template literals.
  3. Async/Await và cách nó làm việc với Promises.
  4. Phương thức push() của Array và giá trị trả về của nó.
  5. Object.freeze() và cách nó tạo ra tính bất biến (immutability) cho objects.

Mỗi khái niệm này đều có những đặc điểm và cách sử dụng riêng, và hiểu rõ chúng sẽ giúp bạn viết code JavaScript hiệu quả và tránh được những lỗi không mong muốn.

Hy vọng bài viết này đã giúp các bạn hiểu rõ hơn về những khía cạnh của JavaScript. Hãy tiếp tục thực hành và áp dụng những kiến thức này vào các dự án của mình nhé!

Nếu có bất kỳ câu hỏi nào, đừng ngại hãy để lại comment bên dưới. Mình sẽ cố gắng trả lời sớm nhất có thể. Cảm ơn các bạn đã theo dõi bài viết! 👋


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í