+9

JavaScript Nâng Cao - Kỳ 21

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. Phép toán OR (||) và truthy/falsy values

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

const one = (false || {} || null)
const two = (null || false || "")
const three = ([] || 0 || true)

console.log(one, two, three)
  • A: false null []
  • B: null "" true
  • C: {} "" []
  • D: null null 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é ❓️

1.1. Phép toán OR (||) trong JavaScript

Phép toán OR (||) trong JavaScript không chỉ đơn thuần là phép toán logic mà còn là một phép toán trả về giá trị. Nó sẽ trả về giá trị truthy đầu tiên mà nó tìm thấy khi đánh giá từ trái sang phải. Nếu tất cả các giá trị đều falsy, nó sẽ trả về giá trị cuối cùng.

1.2. Truthy và Falsy values

Trong JavaScript, một giá trị được coi là truthy nếu nó được đánh giá là true trong một ngữ cảnh boolean. Ngược lại, một giá trị được coi là falsy nếu nó được đánh giá là false trong một ngữ cảnh boolean.

Các giá trị falsy trong JavaScript bao gồm:

  • false
  • 0
  • "" (chuỗi rỗng)
  • null
  • undefined
  • NaN

Tất cả các giá trị khác đều là truthy, bao gồm cả object rỗng {} và mảng rỗng [].

1.3. Phân tích từng biểu thức

  1. const one = (false || {} || null)

    • false là falsy
    • {} là truthy (object rỗng vẫn là truthy)
    • null là falsy
    • Kết quả: {} (giá trị truthy đầu tiên)
  2. const two = (null || false || "")

    • null là falsy
    • false là falsy
    • "" là falsy
    • Kết quả: "" (giá trị cuối cùng vì tất cả đều falsy)
  3. const three = ([] || 0 || true)

    • [] là truthy (mảng rỗng vẫn là truthy)
    • 0 là falsy
    • true là truthy
    • Kết quả: [] (giá trị truthy đầu tiên)

1.4. Tóm lại

Khi sử dụng phép toán OR (||) trong JavaScript, điều quan trọng là phải hiểu rõ về các giá trị truthy và falsy. Phép toán này không chỉ đơn thuần trả về true hoặc false, mà còn trả về giá trị thực tế của toán hạng đầu tiên được đánh giá là truthy.

Việc hiểu rõ cách hoạt động này có thể giúp bạn viết code ngắn gọn và hiệu quả hơn, đặc biệt khi xử lý các giá trị mặc định hoặc khi cần chọn giá trị đầu tiên hợp lệ từ một danh sách các lựa chọn.

2. Promise và Async/Await

Output của đoạn code sau là gì?

const myPromise = () => Promise.resolve('I have resolved!')

function firstFunction() {
  myPromise().then(res => console.log(res))
  console.log('first')
}

async function secondFunction() {
  console.log(await myPromise())
  console.log('second')
}

firstFunction()
secondFunction()
  • A: I have resolved!, firstI have resolved!, second
  • B: first, I have resolved!second, I have resolved!
  • C: I have resolved!, firstsecond, I have resolved!
  • D: first, I have resolved!I have resolved!, second
Đá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é ❓️

2.1. Promise và cách hoạt động

Promise trong JavaScript 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 hoạt động bất đồng bộ. Khi một Promise được tạo ra, nó sẽ ở trạng thái "pending" (đang chờ). Sau đó, nó có thể chuyển sang trạng thái "fulfilled" (hoàn thành) hoặc "rejected" (thất bại).

Trong ví dụ của chúng ta, myPromise là một hàm trả về một Promise đã được resolve ngay lập tức với giá trị 'I have resolved!'.

2.2. Phân tích firstFunction()

Trong firstFunction(), chúng ta có hai dòng code:

  1. myPromise().then(res => console.log(res))
  2. console.log('first')

Mặc dù dòng 1 được gọi trước, nhưng vì nó là một Promise, nó sẽ được đưa vào hàng đợi microtask và chỉ được thực thi sau khi call stack trống. Do đó, dòng 2 sẽ được thực thi trước.

Kết quả của firstFunction() sẽ là:

first
I have resolved!

2.3. Phân tích secondFunction()

Trong secondFunction(), chúng ta sử dụng async/await:

  1. console.log(await myPromise())
  2. console.log('second')

Từ khóa await sẽ tạm dừng thực thi của hàm cho đến khi Promise được resolve. Điều này có nghĩa là dòng 1 sẽ đợi cho đến khi myPromise() hoàn thành trước khi tiếp tục.

Kết quả của secondFunction() sẽ là:

I have resolved!
second

2.4. Thứ tự thực thi

Khi chúng ta gọi cả hai hàm:

firstFunction()
secondFunction()

firstFunction() sẽ được thực thi trước, sau đó đến secondFunction(). Tuy nhiên, do cách hoạt động của Promise và async/await, thứ tự output cuối cùng sẽ là:

first
I have resolved!
I have resolved!
second

2.5. Tóm lại

Hiểu về cách hoạt động của Promise và async/await là rất quan trọng trong JavaScript hiện đại. Chúng cho phép chúng ta xử lý các tác vụ bất đồng bộ một cách hiệu quả và dễ đọc hơn. Tuy nhiên, như chúng ta đã thấy, việc sử dụng chúng cũng có thể dẫn đến những kết quả không mong đợi nếu chúng ta không hiểu rõ cách chúng hoạt động.

3. Set và phép toán cộng (+)

Output của đoạn code sau là gì?

const set = new Set()

set.add(1)
set.add("Lydia")
set.add({ name: "Lydia" })

for (let item of set) {
  console.log(item + 2)
}
  • A: 3, NaN, NaN
  • B: 3, 7, NaN
  • C: 3, Lydia2, [object Object]2
  • D: "12", Lydia2, [object Object]2
Đá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. Set trong JavaScript

Set là một cấu trúc dữ liệu trong JavaScript cho phép bạn lưu trữ các giá trị duy nhất của bất kỳ kiểu dữ liệu nào. Trong ví dụ này, chúng ta đã thêm ba giá trị khác nhau vào Set: một số, một chuỗi và một object.

3.2. Phép toán cộng (+) và type coercion

Phép toán cộng (+) trong JavaScript không chỉ dùng để cộng số mà còn được sử dụng để nối chuỗi. Khi một trong hai toán hạng là chuỗi, JavaScript sẽ cố gắng chuyển đổi toán hạng còn lại thành chuỗi. Quá trình này gọi là type coercion (ép kiểu).

3.3. Phân tích từng phép toán

  1. 1 + 2

    • Cả hai toán hạng đều là số, nên kết quả là phép cộng số học thông thường.
    • Kết quả: 3
  2. "Lydia" + 2

    • Toán hạng đầu tiên là chuỗi, nên 2 sẽ được chuyển thành chuỗi "2".
    • Kết quả của phép nối chuỗi: "Lydia2"
  3. { name: "Lydia" } + 2

    • Khi một object được sử dụng trong phép toán +, nó sẽ được chuyển đổi thành chuỗi "[object Object]".
    • Sau đó, 2 sẽ được chuyển thành chuỗi "2".
    • Kết quả của phép nối chuỗi: "[object Object]2"

3.4. Tại sao không phải là NaN?

Bạn có thể nghĩ rằng "Lydia" + 2 hoặc { name: "Lydia" } + 2 sẽ cho kết quả NaN (Not a Number). Tuy nhiên, đây là một trong những đặc điểm "thú vị" của JavaScript. Khi sử dụng phép toán +, JavaScript ưu tiên nối chuỗi hơn là cộng số học nếu một trong các toán hạng là chuỗi.

3.5. Tóm lại

Hiểu về type coercion và cách JavaScript xử lý các phép toán với các kiểu dữ liệu khác nhau là rất quan trọng. Điều này có thể giúp bạn tránh được những lỗi không mong muốn và viết code chính xác hơn.

Trong thực tế, để tránh những kết quả không mong muốn do type coercion, bạn nên sử dụng các phương thức chuyển đổi kiểu dữ liệu một cách rõ ràng (ví dụ: Number(), String()) hoặc sử dụng các phép toán so sánh nghiêm ngặt (=== thay vì ==).

4. Promise.resolve()

Output của đoạn code sau là gì?

Promise.resolve(5)
  • A: 5
  • B: Promise {<pending>: 5}
  • C: Promise {<fulfilled>: 5}
  • D: Error
Đá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. Promise.resolve()

Promise.resolve() là một phương thức tĩnh của đối tượng Promise trong JavaScript. Nó được sử dụng để tạo ra một Promise đã được resolve (hoàn thành) với một giá trị cụ thể.

4.2. Cách hoạt động của Promise.resolve()

Khi bạn gọi Promise.resolve(value), nó sẽ trả về một Promise đã được resolve với giá trị value. Điều này có nghĩa là:

  1. Promise được tạo ra ngay lập tức ở trạng thái fulfilled (hoàn thành).
  2. Giá trị được truyền vào (5 trong trường hợp này) sẽ là giá trị mà Promise resolve.

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

  • A: 5 - Đây không phải là đáp án đúng vì Promise.resolve(5) không trả về giá trị 5 trực tiếp, mà trả về một Promise.
  • B: Promise {<pending>: 5} - Đây không đúng vì Promise được tạo ra bởi Promise.resolve() không ở trạng thái pending (đang chờ), mà ở trạng thái fulfilled (hoàn thành) ngay lập tức.
  • D: Error - Không có lỗi nào xảy ra khi sử dụng Promise.resolve().

4.4. Ứng dụng thực tế

Promise.resolve() thường được sử dụng trong các tình huống sau:

  1. Khi bạn muốn chuyển đổi một giá trị thông thường thành một Promise.
  2. Trong các hàm async để đảm bảo rằng một giá trị luôn được trả về dưới dạng Promise.
  3. Khi bạn muốn tạo ra một Promise đã hoàn thành ngay lập tức để sử dụng trong các tình huống test hoặc mock data.

4.5. Ví dụ sử dụng

// Sử dụng Promise.resolve() trong một hàm async
async function fetchData() {
  // Giả sử đây là dữ liệu từ API
  const data = { id: 1, name: "John" };
  
  // Trả về dữ liệu dưới dạng Promise đã resolve
  return Promise.resolve(data);
}

// Sử dụng hàm
fetchData().then(result => {
  console.log(result); // { id: 1, name: "John" }
});

4.6. Tóm lại

Hiểu về Promise.resolve() là rất quan trọng khi làm việc với Promise và async/await trong JavaScript. Nó cung cấp một cách thuận tiện để tạo ra các Promise đã hoàn thành, giúp code của bạn dễ đọc và dễ quản lý hơn trong các tình huống bất đồng bộ.

5. So sánh tham chiếu trong JavaScript

Output của đoạn code sau là gì?

function compareMembers(person1, person2 = person) {
  if (person1 !== person2) {
    console.log("Not the same!")
  } else {
    console.log("They are the same!")
  }
}

const person = { name: "Lydia" }

compareMembers(person)
  • A: Not the same!
  • B: They are the same!
  • C: ReferenceError
  • D: SyntaxError
Đá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. Tham số mặc định trong hàm

Trong định nghĩa hàm compareMembers, chúng ta thấy:

function compareMembers(person1, person2 = person) { ... }

Ở đây, person2 được gán giá trị mặc định là person. Điều này có nghĩa là nếu không có đối số thứ hai được truyền vào khi gọi hàm, person2 sẽ nhận giá trị của biến person.

5.2. So sánh tham chiếu của object

Trong JavaScript, khi bạn so sánh hai object bằng toán tử === (hoặc !==), JavaScript sẽ so sánh tham chiếu của chúng, không phải giá trị bên trong.

5.3. Phân tích cách thức hoạt động

  1. Chúng ta tạo một object person với thuộc tính name: "Lydia".
  2. Khi gọi compareMembers(person), chúng ta chỉ truyền một đối số.
  3. person1 nhận giá trị là object person.
  4. person2 không được truyền vào, nên nó nhận giá trị mặc định, cũng chính là object person.
  5. Khi so sánh person1 !== person2, JavaScript so sánh tham chiếu của hai biến này.
  6. Vì cả person1person2 đều trỏ đến cùng một object trong bộ nhớ, nên chúng được coi là giống nhau.

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

  • A: Not the same! - Đây sẽ là kết quả nếu person1person2 trỏ đến các object khác nhau, nhưng trong trường hợp này chúng trỏ đến cùng một object.
  • C: ReferenceError - Không có lỗi tham chiếu nào xảy ra trong đoạn code này.
  • D: SyntaxError - Cú pháp của đoạn code hoàn toàn hợp lệ.

5.5. Ứng dụng và lưu ý

Hiểu về cách JavaScript so sánh object là rất quan trọng, đặc biệt khi làm việc với các cấu trúc dữ liệu phức tạp. Một số điểm cần lưu ý:

  1. Khi so sánh object, luôn nhớ rằng bạn đang so sánh tham chiếu, không phải giá trị.
  2. Nếu muốn so sánh nội dung của object, bạn cần viết hàm so sánh riêng hoặc sử dụng các thư viện như Lodash.
  3. Cẩn thận khi sử dụng tham số mặc định là object hoặc array, vì chúng có thể dẫn đến những kết quả không mong muốn trong một số trường hợp.

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 hoạt động của phép toán OR (||) và khái niệm về truthy/falsy values.
  2. Sự khác biệt giữa Promise .then()async/await trong xử lý bất đồng bộ.
  3. Cách JavaScript xử lý phép toán cộng (+) với các kiểu dữ liệu khác nhau.
  4. Cách sử dụng và ý nghĩa của Promise.resolve().
  5. Cách JavaScript so sánh tham chiếu của object.

Những khái niệm này có vẻ đơn giản nhưng lại rất quan trọng trong việc hiểu và làm chủ JavaScript. 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ề ngôn ngữ lập trình thú vị này.

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" nhé!

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í