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ộtcomment 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").value
vàgame.next().value
- B:
game.next.value("Yes")
vàgame.next.value()
- C:
game.next().value
vàgame.next("Yes").value
- D:
game.next.value()
và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:
- Nó tạm dừng thực thi hàm.
- Nó trả về giá trị được chỉ định sau
yield
. - 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:
const game = startGame();
khởi tạo generator.console.log(/* 1 */);
- Ở đây, chúng ta cần gọigame.next().value
. Điều này sẽ chạy generator đếnyield
đầu tiên và trả về "Do you love JavaScript?".console.log(/* 2 */);
- Ở đây, chúng ta cần gọigame.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 ❤️".
- Truyền "Yes" vào generator, gán nó cho
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:
Hello\nworld
là một template literal.- Thông thường,
\n
sẽ được hiểu là ký tự xuống dòng. - 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
getData()
là một async function, nên nó luôn trả về một Promise.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.const data = getData();
gọi hàm getData nhưng không đợi nó hoàn thành.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
- Ban đầu, mảng
["banana"]
có độ dài là 1. list.push(item)
thêm "apple" vào mảng và trả về độ dài mới của mảng.- Hàm
addToList
trả về kết quả củalist.push(item)
, tức là độ dài mới của mảng. const result = addToList("apple", ["banana"])
gán giá trị trả về củaaddToList
choresult
.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:
- Không thể thêm thuộc tính mới.
- Không thể xóa hoặc thay đổi các thuộc tính hiện có.
- 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
const box = { x: 10, y: 20 };
tạo một đối tượngbox
.Object.freeze(box);
đóng băng đối tượngbox
.const shape = box;
tạo một tham chiếu mới đến cùng một đối tượng đã bị đóng băng.shape.x = 100;
cố gắng thay đổi giá trị củax
, nhưng không thành công vì đối tượng đã bị đóng băng.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ề:
- Generator Functions và cách sử dụng
yield
. String.raw
và cách nó xử lý các ký tự escape trong template literals.- Async/Await và cách nó làm việc với Promises.
- Phương thức
push()
của Array và giá trị trả về của nó. 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! 👋