+9

JavaScript Nâng Cao - Kỳ 26

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

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. Intl.NumberFormat và định dạng số

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

function getFine(speed, amount) {
  const formattedSpeed = new Intl.NumberFormat({
    'en-US',
    { style: 'unit', unit: 'mile-per-hour' }
  }).format(speed)

  const formattedAmount = new Intl.NumberFormat({
    'en-US',
    { style: 'currency', currency: 'USD' }
  }).format(amount)

  return `The driver drove ${formattedSpeed} and has to pay ${formattedAmount}`
}

console.log(getFine(130, 300))
  • A: The driver drove 130 and has to pay 300
  • B: The driver drove 130 mph and has to pay $300.00
  • C: The driver drove undefined and has to pay undefined
  • D: The driver drove 130.00 and has to pay 300.00
Đá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é ❓️

1.1. Intl.NumberFormat là gì?

Intl.NumberFormat là một đối tượng trong JavaScript được sử dụng để định dạng số theo các quy ước ngôn ngữ cụ thể. Nó cho phép chúng ta định dạng số, tiền tệ và phần trăm theo cách phù hợp với từng vùng miền và ngôn ngữ.

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

Trong đoạn code trên, chúng ta có hai lần sử dụng Intl.NumberFormat:

  1. Để định dạng tốc độ:
new Intl.NumberFormat({
  'en-US',
  { style: 'unit', unit: 'mile-per-hour' }
}).format(speed)

Ở đây, chúng ta đang định dạng số speed theo kiểu đơn vị (unit) với đơn vị là dặm trên giờ (mile-per-hour).

  1. Để định dạng số tiền:
new Intl.NumberFormat({
  'en-US',
  { style: 'currency', currency: 'USD' }
}).format(amount)

Ở đây, chúng ta đang định dạng số amount theo kiểu tiền tệ (currency) với đơn vị là đô la Mỹ (USD).

1.3. Kết quả

Khi chúng ta gọi getFine(130, 300), hàm sẽ trả về chuỗi:

"The driver drove 130 mph and has to pay $300.00"

  • 130 mph: Tốc độ được định dạng với đơn vị dặm trên giờ.
  • $300.00: Số tiền được định dạng theo kiểu tiền tệ đô la Mỹ.

1.4. Lưu ý

Việc sử dụng Intl.NumberFormat giúp chúng ta dễ dàng định dạng số theo các quy ước địa phương, tăng tính thân thiện với người dùng và đảm bảo tính nhất quán trong việc hiển thị số liệu trên toàn ứng dụng.

2. Destructuring và Array Manipulation

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

const spookyItems = ["👻", "🎃", "🕸"];
({ item: spookyItems[3] } = { item: "💀" });

console.log(spookyItems);
  • A: ["👻", "🎃", "🕸"]
  • B: ["👻", "🎃", "🕸", "💀"]
  • C: ["👻", "🎃", "🕸", { item: "💀" }]
  • D: ["👻", "🎃", "🕸", "[object Object]"]
Đá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. Destructuring Assignment

Destructuring assignment là một cú pháp trong JavaScript cho phép chúng ta "unpack" các giá trị từ arrays hoặc properties từ objects vào các biến riêng biệt.

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

Trong đoạn code trên, chúng ta có:

({ item: spookyItems[3] } = { item: "💀" });

Đây là một destructuring assignment đặc biệt. Nó có thể được hiểu như sau:

  1. Bên phải dấu = là một object { item: "💀" }.
  2. Bên trái dấu = là một pattern destructuring { item: spookyItems }.
  3. Cú pháp này gán giá trị của item (là "💀") cho spookyItems.

2.3. Kết quả

Sau khi thực hiện destructuring assignment, mảng spookyItems sẽ được cập nhật:

  • Ban đầu: ["👻", "🎃", "🕸"]
  • Sau khi gán: ["👻", "🎃", "🕸", "💀"]

Phần tử thứ 4 (index 3) của mảng được thêm vào với giá trị "💀".

2.4. Lưu ý

Đây là một cách khá "hack não" để thêm phần tử vào mảng. Trong thực tế, chúng ta thường sử dụng các phương thức như push() hoặc gán trực tiếp spookyItems = "💀" để thêm phần tử vào mảng.

Destructuring assignment là một công cụ mạnh mẽ trong JavaScript, nhưng cũng cần sử dụng cẩn thận để tránh tạo ra code khó đọc hoặc khó hiểu.

3. Number.isNaN vs isNaN

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

const name = "Lydia Hallie";
const age = 21;

console.log(Number.isNaN(name));
console.log(Number.isNaN(age));

console.log(isNaN(name));
console.log(isNaN(age));
  • A: true false true false
  • B: true false false false
  • C: false false true false
  • D: false true false true
Đá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. Number.isNaN() vs isNaN()

Có hai hàm để kiểm tra NaN trong JavaScript: Number.isNaN()isNaN(). Mặc dù chúng có tên gần giống nhau, nhưng cách hoạt động của chúng khá khác biệt.

3.2. Number.isNaN()

Number.isNaN() kiểm tra xem giá trị được truyền vào có phải là NaN (Not-a-Number) hay không. Nó trả về true nếu giá trị là NaN, và false cho mọi giá trị khác.

Đặc điểm quan trọng: Number.isNaN() không thực hiện bất kỳ chuyển đổi kiểu dữ liệu nào.

3.3. isNaN()

isNaN() kiểm tra xem giá trị được truyền vào có phải là NaN hay không, nhưng nó có một bước trung gian: nó sẽ cố gắng chuyển đổi giá trị thành số trước khi kiểm tra.

3.4. Phân tích kết quả

  1. Number.isNaN(name): false

    • name là một chuỗi, không phải NaN.
  2. Number.isNaN(age): false

    • age là một số, không phải NaN.
  3. isNaN(name): true

    • isNaN() cố gắng chuyển "Lydia Hallie" thành số, kết quả là NaN.
  4. isNaN(age): false

    • age đã là một số, nên isNaN() trả về false.

3.5. Lưu ý

  • Number.isNaN() an toàn hơn và chính xác hơn trong việc kiểm tra NaN.
  • isNaN() có thể gây nhầm lẫn với các giá trị không phải số nhưng có thể chuyển đổi thành số.

Ví dụ:

isNaN("123")  // false, vì "123" có thể chuyển thành số
isNaN("hello")  // true, vì "hello" không thể chuyển thành số

Trong thực tế, nên ưu tiên sử dụng Number.isNaN() để tránh những kết quả không mong muốn.

4. Temporal Dead Zone (TDZ)

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

const randomValue = 21;

function getInfo() {
 console.log(typeof randomValue);
 const randomValue = "Lydia Hallie";
}

getInfo();
  • A: "number"
  • B: "string"
  • C: undefined
  • D: ReferenceError
Đá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é ❓️

4.1. Temporal Dead Zone (TDZ) là gì?

Temporal Dead Zone (TDZ) là khoảng thời gian từ khi một biến được khai báo (bằng let hoặc const) cho đến khi nó được khởi tạo. Trong khoảng thời gian này, nếu ta cố gắng truy cập biến, JavaScript sẽ throw một ReferenceError.

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

Trong đoạn code trên, chúng ta có:

  1. Một biến randomValue được khai báo ở scope global với giá trị 21.
  2. Một hàm getInfo() được định nghĩa.
  3. Bên trong getInfo(), chúng ta cố gắng log ra typeof randomValue trước khi khai báo một biến local cũng tên là randomValue.

4.3. Tại sao lại xảy ra lỗi?

Khi JavaScript engine thực thi code bên trong getInfo(), nó "biết" rằng có một biến local randomValue được khai báo (nhờ vào hoisting). Tuy nhiên, biến này đang ở trong TDZ cho đến khi nó được khởi tạo.

Khi chúng ta cố gắng truy cập randomValue (thông qua typeof randomValue), JavaScript không tìm kiếm biến global randomValue, mà thay vào đó, nó nhận ra rằng có một biến local randomValue đang trong TDZ. Do đó, một ReferenceError được throw.

4.4. Làm thế nào để sửa?

Để sửa lỗi này, chúng ta có thể:

  1. Xóa khai báo local của randomValue trong getInfo():
function getInfo() {
 console.log(typeof randomValue);  // Sẽ log "number"
}
  1. Hoặc di chuyển console.log sau khai báo:
function getInfo() {
 const randomValue = "Lydia Hallie";
 console.log(typeof randomValue);  // Sẽ log "string"
}

4.5. Lưu ý

TDZ là một khái niệm quan trọng trong JavaScript, đặc biệt khi làm việc với letconst. Nó giúp ngăn chặn việc sử dụng biến trước khi chúng được khởi tạo, từ đó tránh được nhiều lỗi tiềm ẩn.

5. Async/Await, Try/Catch/Finally

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

const myPromise = Promise.resolve("Woah some cool data");

(async () => {
 try {
  console.log(await myPromise);
 } catch {
  throw new Error(`Oops didn't work`);
 } finally {
  console.log("Oh finally!");
 }
})();
  • A: Woah some cool data
  • B: Oh finally!
  • C: Woah some cool data Oh finally!
  • D: Oops didn't work Oh finally!
Đá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. Async/Await

Async/Await là một cú pháp trong JavaScript giúp làm việc với Promises dễ dàng hơn. Nó cho phép chúng ta viết code bất đồng bộ theo cách tương tự như code đồng bộ.

5.2. Try/Catch/Finally

  • try: Chứa code có thể gây ra lỗi.
  • catch: Xử lý lỗi nếu có.
  • finally: Luôn được thực thi, bất kể có lỗi hay không.

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

  1. Chúng ta có một Promise đã được resolve với giá trị "Woah some cool data".
  2. Sử dụng một IIFE (Immediately Invoked Function Expression) async để xử lý Promise.
  3. Trong khối try, chúng ta await Promise và log kết quả.
  4. Nếu có lỗi, khối catch sẽ throw một Error mới.
  5. Cuối cùng, khối finally sẽ log "Oh finally!".

5.4. Diễn biến thực thi

  1. myPromise resolve ngay lập tức với giá trị "Woah some cool data".
  2. Trong khối try, await myPromise trả về giá trị đã resolve.
  3. console.log in ra "Woah some cool data".
  4. Không có lỗi xảy ra, nên khối catch không được thực thi.
  5. Khối finally luôn được thực thi, in ra "Oh finally!".

5.5. Kết quả

Output sẽ là:

Woah some cool data
Oh finally!

5.6. Lưu ý

  • Async/Await giúp code dễ đọc hơn khi làm việc với Promises.
  • Khối finally luôn được thực thi, bất kể có lỗi hay không. Nó thường được sử dụng để cleanup resources hoặc thực hiện các tác vụ cần thiết sau khi xử lý Promise.
  • Trong trường hợp này, vì Promise đã được resolve trước, nên không có lỗi xảy ra và khối catch không được thực thi.

Async/Await kết hợp với try/catch/finally là một pattern mạnh mẽ để xử lý các tác vụ bất đồng bộ và xử lý lỗi một cách hiệu quả trong JavaScript.

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 nâng cao của JavaScript:

  1. Sử dụng Intl.NumberFormat để định dạng số theo ngôn ngữ và vùng miền.
  2. Destructuring assignment và cách nó có thể được sử dụng để thao tác với mảng.
  3. Sự khác biệt giữa Number.isNaN()isNaN().
  4. Temporal Dead Zone (TDZ) và cách nó ảnh hưởng đến việc khai báo biến.
  5. Async/Await kết hợp với try/catch/finally để xử lý Promises và lỗi.

Mỗi câu hỏi đều có những ứng dụng và lưu ý riêng trong việc phát triển ứng dụng JavaScript. Việc hiểu rõ những khái niệm này sẽ giúp bạn viết code hiệu quả hơn và tránh được nhiều lỗi tiềm ẩn.

Hy vọng bài viết này đã mang lại cho bạn những kiến thức bổ ích. Hãy tiếp tục thực hành và áp dụng những kiến thức này vào dự án của bạn để trở thành một lập trình viên JavaScript giỏi hơn 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
Let's register a Viblo Account to get more interesting posts.