JavaScript Nâng Cao - Kỳ 17
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. Tham số mặc định trong hàm
Output của đoạn code bên dưới là gì?
function sayHi(name) {
return `Hi there, ${name}`
}
console.log(sayHi())
- A:
Hi there,
- B:
Hi there, undefined
- C:
Hi there, null
- 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é ❓️
1.1. Phân tích vấn đề
Trong JavaScript, khi chúng ta định nghĩa một hàm với tham số nhưng không truyền giá trị cho tham số đó khi gọi hàm, JavaScript sẽ tự động gán giá trị undefined
cho tham số đó.
Trong trường hợp này, hàm sayHi
được định nghĩa với một tham số name
, nhưng khi gọi hàm sayHi()
, chúng ta không truyền bất kỳ đối số nào. Do đó, name
sẽ nhận giá trị undefined
.
1.2. Template literals và undefined
Khi chúng ta sử dụng template literals (chuỗi được bao quanh bởi backticks ``), JavaScript sẽ tự động chuyển đổi các giá trị không phải chuỗi thành chuỗi. Trong trường hợp này, undefined
sẽ được chuyển thành chuỗi "undefined".
Vì vậy, kết quả của Hi there, ${name}
sẽ là "Hi there, undefined"
.
1.3. Giá trị mặc định cho tham số
Để tránh tình huống này, chúng ta có thể sử dụng giá trị mặc định cho tham số. Ví dụ:
function sayHi(name = "Guest") {
return `Hi there, ${name}`
}
console.log(sayHi()) // "Hi there, Guest"
console.log(sayHi("Lydia")) // "Hi there, Lydia"
Trong ví dụ này, nếu không có giá trị nào được truyền vào cho name
, nó sẽ sử dụng giá trị mặc định "Guest".
1.4. Tóm lại
Hiểu về cách JavaScript xử lý các tham số không được truyền giá trị là rất quan trọng để tránh các lỗi không mong muốn trong code của bạn. Luôn nhớ rằng:
- Tham số không được truyền giá trị sẽ có giá trị
undefined
. - Sử dụng giá trị mặc định cho tham số là một cách tốt để xử lý trường hợp tham số không được truyền giá trị.
1.5 Ví dụ thêm
Đây là một ví dụ khác về việc sử dụng giá trị mặc định cho tham số:
const readOnlyClient = {
query: () => "query from read only client"
}
const writeClient = {
query: () => "query from write client"
}
async function getData(query, client = readOnlyClient) {
return client.query(query)
}
await getData("SELECT * FROM users") // "query" with readOnlyClient
await getData("DELETE * FROM users", writeClient) // "query" with writeClient
Trong ví dụ này, hàm getData
sẽ trả về kết quả từ client.query(query)
. Nếu không có client
nào được truyền vào, nó sẽ sử dụng readOnlyClient
mặc định. Điều này giúp chúng ta dễ dàng thay đổi client mà không cần thay đổi code của hàm getData
.
2. This trong các phương thức và hàm
Output của đoạn code bên dưới là gì?
var status = "😎"
setTimeout(() => {
const status = "😍"
const data = {
status: "🥑",
getStatus() {
return this.status
}
}
console.log(data.getStatus())
console.log(data.getStatus.call(this))
}, 0)
- A:
"🥑"
and"😍"
- B:
"🥑"
and"😎"
- C:
"😍"
and"😎"
- D:
"😎"
and"😎"
Đá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 đề
Đoạn code này liên quan đến một số khái niệm quan trọng trong JavaScript:
- Phạm vi của biến (variable scope)
- Từ khóa
this
- Phương thức
call()
2.2. Phạm vi của biến
Trong đoạn code, chúng ta có ba biến status
:
- Biến global
var status = "😎"
- Biến local
const status = "😍"
trong callback củasetTimeout
- Thuộc tính
status: "🥑"
của objectdata
2.3. Từ khóa this
trong phương thức
Trong phương thức getStatus
của object data
, this
sẽ trỏ đến chính object data
. Do đó, this.status
sẽ trả về "🥑"
.
console.log(data.getStatus()) // "🥑"
2.4. Phương thức call()
Phương thức call()
cho phép chúng ta gọi một hàm với một giá trị this
được chỉ định. Trong trường hợp này:
console.log(data.getStatus.call(this))
this
ở đây là this
của arrow function trong setTimeout
. Arrow function không có this
riêng, nó sẽ sử dụng this
của context bên ngoài, trong trường hợp này là global context. Trong global context, this
trỏ đến global object (trong trình duyệt là window
), và status
global là "😎"
.
2.5. Tóm lại
data.getStatus()
trả về"🥑"
vìthis
trong phương thức trỏ đến objectdata
.data.getStatus.call(this)
trả về"😎"
vìthis
được chỉ định làthis
của context global.
Hiểu về cách this
hoạt động trong các ngữ cảnh khác nhau là rất quan trọng trong JavaScript. Nó giúp chúng ta kiểm soát được context của hàm và tránh các lỗi không mong muốn.
Chú ý: Trong JavaScript,
this
là một trong những khái niệm hơi phức tạp. Tuy nhiên bạn chỉ cần nhớ 2 quy tắc sau:
Arrow function không có
this
riêng, nó sẽ sử dụngthis
của context bên ngoài.Trong phương thức của một object,
this
sẽ trỏ đến object gọi phương thức đó.
3. Tham chiếu và gán giá trị
Output của đoạn code bên dưới là gì?
const person = {
name: "Lydia",
age: 21
}
let city = person.city
city = "Amsterdam"
console.log(person)
- A:
{ name: "Lydia", age: 21 }
- B:
{ name: "Lydia", age: 21, city: "Amsterdam" }
- C:
{ name: "Lydia", age: 21, city: undefined }
- D:
"Amsterdam"
Đáp án của câu hỏi này là
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
↓↓↓↓↓↓↓↓↓↓
Đáp án: A
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 đề
Đoạn code này liên quan đến cách JavaScript xử lý việc gán giá trị và tham chiếu đối với objects.
3.2. Gán giá trị từ thuộc tính không tồn tại
Khi chúng ta thực hiện:
let city = person.city
Chúng ta đang cố gắng truy cập thuộc tính city
của object person
. Tuy nhiên, person
không có thuộc tính city
. Trong JavaScript, khi chúng ta truy cập một thuộc tính không tồn tại của một object, kết quả sẽ là undefined
.
Vì vậy, sau dòng code này, city
sẽ có giá trị undefined
.
3.3. Gán giá trị mới cho biến
Tiếp theo, chúng ta có:
city = "Amsterdam"
Ở đây, chúng ta đang gán một giá trị mới cho biến city
. Điều này không ảnh hưởng gì đến object person
ban đầu. Chúng ta chỉ đang thay đổi giá trị của một biến độc lập.
3.4. Không có sự thay đổi đối với object ban đầu
Quan trọng là phải hiểu rằng không có bất kỳ thao tác nào trong đoạn code này thực sự thay đổi object person
. Chúng ta không thêm thuộc tính mới hay sửa đổi bất kỳ thuộc tính nào của person
.
3.5. Tóm lại
- Việc truy cập
person.city
trả vềundefined
vì thuộc tính này không tồn tại. - Gán "Amsterdam" cho
city
chỉ thay đổi giá trị của biếncity
, không ảnh hưởng đếnperson
. - Object
person
vẫn giữ nguyên như ban đầu.
Hiểu về cách JavaScript xử lý objects và tham chiếu là rất quan trọng. Trong trường hợp này, chúng ta thấy rằng việc gán giá trị cho một biến độc lập không ảnh hưởng đến object gốc, ngay cả khi giá trị ban đầu của biến đó được lấy từ object.
4. Block scope và biến
Output của đoạn code bên dưới là gì?
function checkAge(age) {
if (age < 18) {
const message = "Sorry, you're too young."
} else {
const message = "Yay! You're old enough!"
}
return message
}
console.log(checkAge(21))
- A:
"Sorry, you're too young."
- B:
"Yay! You're old enough!"
- C:
ReferenceError
- 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é ❓️
4.1. Phân tích vấn đề
Đoạn code này liên quan đến khái niệm quan trọng trong JavaScript: block scope (phạm vi khối).
4.2. Block scope trong JavaScript
Trong JavaScript, các biến được khai báo bằng let
và const
có phạm vi block. Điều này có nghĩa là chúng chỉ tồn tại trong block (khối) mà chúng được khai báo, bao gồm cả các block con.
Một block trong JavaScript được xác định bởi cặp dấu ngoặc nhọn {}
. Ví dụ, mỗi câu lệnh if
và else
tạo ra một block riêng.
4.3. Vấn đề với biến message
Trong hàm checkAge
, biến message
được khai báo bên trong các block if
và else
:
if (age < 18) {
const message = "Sorry, you're too young."
} else {
const message = "Yay! You're old enough!"
}
Điều này có nghĩa là biến message
chỉ tồn tại bên trong các block này. Khi chúng ta cố gắng truy cập message
bên ngoài các block (trong câu lệnh return
), JavaScript không thể tìm thấy biến này.
4.4. ReferenceError
Khi JavaScript không thể tìm thấy một biến đã được tham chiếu, nó sẽ throw một ReferenceError
. Đó chính xác là những gì xảy ra trong trường hợp này.
4.5. Cách khắc phục
Để khắc phục vấn đề này, chúng ta có thể khai báo biến message
bên ngoài các block if/else
:
function checkAge(age) {
let message;
if (age < 18) {
message = "Sorry, you're too young."
} else {
message = "Yay! You're old enough!"
}
return message
}
console.log(checkAge(21)) // "Yay! You're old enough!"
Bằng cách này, message
sẽ tồn tại trong phạm vi của toàn bộ hàm checkAge
, và chúng ta có thể truy cập nó trong câu lệnh return
.
4.6. Tóm lại
- Biến được khai báo với
let
vàconst
có phạm vi block. - Cố gắng truy cập biến ngoài phạm vi của nó sẽ dẫn đến
ReferenceError
. - Để sử dụng một biến trong toàn bộ hàm, hãy khai báo nó ở đầu hàm, ngoài các block con.
Hiểu về phạm vi của biến là rất quan trọng trong JavaScript. Nó giúp chúng ta tránh được nhiều lỗi không mong muốn và viết code chặt chẽ, an toàn hơn.
5. Promises và Fetch API
Những thông tin nào sẽ được ghi ra với đoạn code sau?
fetch('https://www.website.com/api/user/1')
.then(res => res.json())
.then(res => console.log(res))
- A: Kết quả của phương thức
fetch
. - B: Kết quả của lần gọi thứ hai đến phương thức
fetch
. - C: Kết quả của callback trong
.then()
trước đó. - D: Luôn là 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é ❓️
5.1. Phân tích vấn đề
Đoạn code này liên quan đến Fetch API và Promises trong JavaScript, hai công cụ mạnh mẽ để xử lý các yêu cầu bất đồng bộ.
5.2. Fetch API
fetch()
là một phương thức modern của JavaScript để thực hiện các yêu cầu HTTP. Nó trả về một Promise mà resolve với một đối tượng Response.
5.3. Chuỗi Promise
Trong đoạn code, chúng ta thấy một chuỗi các phương thức .then()
. Mỗi .then()
nhận kết quả từ Promise trước đó và trả về một Promise mới.
5.4. Phân tích từng bước
-
fetch('https://www.website.com/api/user/1')
:- Gửi một yêu cầu GET đến URL được chỉ định.
- Trả về một Promise resolve với đối tượng Response.
-
.then(res => res.json())
:- Nhận đối tượng Response từ fetch.
- Gọi phương thức
json()
trên Response, trả về một Promise mới. - Promise này resolve với dữ liệu JSON đã được parse.
-
.then(res => console.log(res))
:- Nhận dữ liệu JSON đã được parse từ Promise trước đó.
- Log dữ liệu này ra console.
5.5. Kết quả
Kết quả được log ra console sẽ là dữ liệu JSON đã được parse từ response của API. Đây chính là "kết quả của callback trong .then()
trước đó".
5.6. Tóm lại
- Fetch API trả về một Promise với đối tượng Response.
- Phương thức
json()
parse body của Response thành JSON. - Chuỗi
.then()
cho phép chúng ta xử lý dữ liệu qua nhiều bước. - Kết quả cuối cùng là dữ liệu JSON đã được parse.
Hiểu về cách làm việc với Promises và Fetch API là rất quan trọng trong phát triển web hiện đại. Nó cho phép chúng ta xử lý các tác vụ bất đồng bộ một cách dễ dàng và hiệu quả.
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 quan trọng của JavaScript:
- Tham số mặc định trong hàm
- Cách
this
hoạt động trong các ngữ cảnh khác nhau - Tham chiếu và gán giá trị cho objects
- Block scope và phạm vi của biến
- Làm việc với Promises và Fetch API
Mỗi khía cạnh này đều có những nét đặc trưng riêng và đóng vai trò quan trọng trong việc viết code JavaScript hiệu quả và tránh bugs.
Hãy nhớ rằng, JavaScript là một ngôn ngữ rất linh hoạt và đôi khi có những hành vi không mong đợi. Việc hiểu rõ các khái niệm cơ bản và thực hành thường xuyên sẽ giúp bạn trở thành một lập trình viên JavaScript giỏi hơn.
Hy vọng bài viết này đã giúp bạn hiểu rõ hơn về JavaScript. 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ộ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. 🤗
All rights reserved