Những câu hỏi hay trong Javascript Phần 5
Tiếp nối với 3 phần trước trong series Những câu hỏi hay trong Javascript, rất vui khi trở lại đây cùng các bạn trong phần 4, dưới đây sẽ là danh sách các câu hỏi và đáp án theo thứ tự tiếp nối với phần 1, 2 và 3.
101. Lexical Scope là gì?
let name = 'Lydia'; function getName() { console.log(name); let name = 'Sarah'; } getName();
A: Lydia
B: Sarah
C: undefined
D: ReferenceError
Mỗi hàm có execution scope(hoặc phạm vi) riêng. Trước tiên, hàm getName
xét trong ngữ cảnh (phạm vi) của chính nó để xem nó có chứa tên biến mà chúng ta đang cố truy cập hay không. Trong trường hợp này, hàm getName
chứa biến name
của nó: chúng ta khai báo tên biến với từ khóa let
và với giá trị là 'Sarah'
Các biến có từ khóa let
(và const
) được đưa lên đầu scope, nhưng không giống như var
, chúng không thể truy cập trước khi chúng ta khai báo (khởi tạo) chúng. Khi chúng ta cố gắng truy cập các biến trước khi chúng được khai báo, JavaScript sẽ ném ra một ReferenceError
.
Nếu chúng ta không khai báo biến tên trong hàm getName
, thì javascript engine sẽ xem xét scope chain (chuỗi phạm vi). Phạm vi bên ngoài có một biến được gọi là tên với giá trị là Lydia
. Trong trường hợp đó, nó sẽ ghi lại Lydia
.
102. yield
và yield*
trong Generator function
function* generatorOne() { yield ['a', 'b', 'c']; } function* generatorTwo() { yield* ['a', 'b', 'c']; } const one = generatorOne(); const two = generatorTwo(); console.log(one.next().value); console.log(two.next().value);
A: a and a
B: a and undefined
C: ['a', 'b', 'c'] and a
D: a and ['a', 'b', 'c']
Với từ khóa yield
, chúng mang lại giá trị trong hàm. Với từ khóa yield*
, chúng có thể mang lại các giá trị từ một hàm generator khác hoặc đối tượng có thể lặp lại (ví dụ một mảng).
Trong generatorOne
, chúng ta tạo ra toàn bộ mảng ['a', 'b', 'c']
bằng cách sử dụng từ khóa yield
. Giá trị của thuộc tính value trên đối tượng được phương thức next
trả về trên one
(one.next (). Value)
bằng toàn bộ mảng ['a', 'b', 'c']
.
console.log(one.next().value); // ['a', 'b', 'c'] console.log(one.next().value); // undefined
Trong generatorTwo
chúng ta sử dụng từ khóa yield*
. Điều này có nghĩa là giá trị mang lại đầu tiên của two
, bằng với giá trị mang lại đầu tiên trong vòng lặp. Trình lặp là mảng ['a', 'b', 'c']
. Giá trị được tạo ra đầu tiên là a
, vì vậy lần đầu tiên chúng ta gọi giá trị two.next().value
., a
được trả về.
103. Thay đổi giá trị của một biến khi được gán setInterval
let config = { alert: setInterval(() => { console.log('Alert!'); }, 1000), }; config = null;
A: The setInterval callback won't be invoked
B: The setInterval callback gets invoked once
C: The setInterval callback will still be called every second
D: We never invoked config.alert(), config is null
Thông thường khi chúng ta đặt các đối tượng bằng null
, các đối tượng đó sẽ được thu gom rác vì không còn tham chiếu đến đối tượng đó nữa. Tuy nhiên, vì hàm gọi lại trong setInterval là một arrow function (do đó được liên kết với đối tượng cấu hình), nên hàm gọi lại vẫn giữ một tham chiếu đến đối tượng cấu hình, hàm gọi lại vẫn giữ một tham chiếu đến đối tượng cấu hình.
Miễn là có một tham chiếu, đối tượng sẽ không bị thu thập rác. Vì đây là khoảng thời gian, việc đặt cấu hình thành null hoặc xóa config.alert
sẽ không thu thập khoảng thời gian này, vì vậy khoảng thời gian sẽ vẫn được gọi. Nó phải được xóa bằng clearInterval
(config.alert)
để xóa nó khỏi bộ nhớ. Vì nó chưa bị xóa, nên hàm gọi lại setInterval
sẽ vẫn được gọi sau mỗi 1000ms (1s)
.
104. phương thức nào sẽ trả về giá trị 'Hello world!'?
const myMap = new Map(); const myFunc = () => 'greeting'; myMap.set(myFunc, 'Hello world!'); //1 myMap.get('greeting'); //2 myMap.get(myFunc); //3 myMap.get(() => 'greeting');
A: 1
B: 2
C: 2 and 3
D: All of them
Khi thêm một cặp key
hoặc giá trị tương ứng bằng phương thức set
, key
sẽ là giá trị của đối số đầu tiên được truyền cho hàm set
và giá trị sẽ là đối số thứ hai được truyền cho hàm set
. Ở VD trên, key
là hàm () => 'greeting'
và giá trị 'Xin chào thế giới'. myMap
bây giờ là {() => 'welcome' => 'Hello world!' }.
Đáp án 1
là sai, vì key
không phải là 'welcome'
mà là () => 'welcome'
. 3
là sai, vì chúng ta đang tạo một hàm mới bằng cách chuyển nó làm tham số cho phương thức get
. Đối tượng tương tác bằng tham chiếu. Các hàm là các đối tượng, đó là lý do tại sao hai hàm không bao giờ hoàn toàn bằng nhau, ngay cả khi chúng giống hệt nhau: chúng có tham chiếu đến một vị trí khác trong bộ nhớ.
105. Thay đổi giá trị của object thông qua hàm
const person = { name: 'Lydia', age: 21, }; const changeAge = (x = { ...person }) => (x.age += 1); const changeAgeAndName = (x = { ...person }) => { x.age += 1; x.name = 'Sarah'; }; changeAge(person); changeAgeAndName(); console.log(person);
A: {name: "Sarah", age: 22}
B: {name: "Sarah", age: 23}
C: {name: "Lydia", age: 22}
D: {name: "Lydia", age: 23}
Cả hai hàm changeAge
và changeAgeAndName
đều có một tham số mặc định, cụ thể là một đối tượng mới được tạo {... person}
. Đối tượng này có các bản sao của tất cả các key/value
trong đối tượng person
.
Đầu tiên, chúng ta gọi hàm changeAge
và truyền đối tượng person
làm tham số của nó. Hàm này tăng giá trị của thuộc tính age
lên 1 => person
hiện là {name: "Lydia", age: 22}
.
Sau đó, chúng ta gọi hàm changeAgeAndName
, tuy nhiên chúng ta không truyền một tham số. Thay vào đó, giá trị của x bằng một đối tượng mới: {... person}
. Vì là một đối tượng mới nên nó không ảnh hưởng đến giá trị của các thuộc tính trên đối tượng person
. Do đó, person
vẫn bằng {name: "Lydia", age: 22}
.
106. Tách thành các phần tử trong một mảng thông qua spread operator
function sumValues(x, y, z) { return x + y + z; }
A: sumValues([...1, 2, 3])
B: sumValues([...[1, 2, 3]])
C: sumValues(...[1, 2, 3])
D: sumValues([1, 2, 3])
Với toán tử ...
, chúng ta có thể tách mảng thành các phần tử. Hàm sumValues
nhận 3 tham số x
, y
, z
. ...[1,2,3]
sẽ tạo thành 1,2,3
sẽ truyền tương ứng với 3 tham số trong mảng.
107. Toán tử ?.
có tác dụng gì?
const person = { firstName: 'Lydia', lastName: 'Hallie', pet: { name: 'Mara', breed: 'Dutch Tulip Hound', }, getFullName() { return `${this.firstName} ${this.lastName}`; }, }; console.log(person.pet?.name); console.log(person.pet?.family?.name); console.log(person.getFullName?.()); console.log(member.getLastName?.());
A: undefined undefined undefined undefined
B: Mara undefined Lydia Hallie ReferenceError
C: Mara null Lydia Hallie null
D: null ReferenceError null ReferenceError
Với toán tử chuỗi tùy chọn .?
, chúng ta không cần phải khai báo tường minh liệu rằng giá trị được lồng sâu bên trong object có hợp lệ hay không. Nếu chúng ta cố gắng truy cập vào thuộc tính của một biến undefined
hoặc null
biểu thức sẽ trả về undefined
person.pet?.name
: trong đối tượng person
có thuộc tính là pet
, person.pet
không null
, trong nó có thuộc tính là name
, do đó truy vấn thành công và trả về Mara
.
Tiếp theo, person.pet?.family.name
: person
có thuộc tính pet
, nên person.pet
không null, nhưng trong person.pet
lại không có thuộc tính family
. Do đó, từ vị trí này trở đí, nó sẽ trả về undefined
.
Tương tự với các trường hợp tiếp theo.
108. Lưu ý khi truy vấn mảng với indexOf
const groceries = ['banana', 'apple', 'peanuts']; if (groceries.indexOf('banana')) { console.log('We have to buy bananas!'); } else { console.log(`We don't have to buy bananas!`); }
A: We have to buy bananas!
B: We don't have to buy bananas
C: undefined
D: 1
Chúng tôi đã truyền groceries.indexOf ("banana")
vào câu lệnh biểu thức điều kiện if
. groceries.indexOf ("banana")
trả về 0
, là một giá trị sai
trong lập trình. Vì điều kiện trong câu lệnh if
là sai
, nên code trong khối khác sẽ trả về We don't have to buy bananas!
.
109. so sánh kiểu dữ liệu với !typeof
const name = 'Lydia Hallie'; console.log(!typeof name === 'object'); console.log(!typeof name === 'string');
A: false true
B: true false
C: false false
D: true true
typeof name
trả về kiểu string
. Chuỗi string
là giá trị "string"
đúng nghĩa. vì vậy !typeof name
trả về giá trị kiệu boolean là false
. false === "object"
và false === "string"
cà 2 đều trả về false
.
110. Kết quả dưới đây là gì?
const myFunc = ({ x, y, z }) => { console.log(x, y, z); }; myFunc(1, 2, 3);
A: 1 2 3
B: {1: 1} {2: 2} {3: 3}
C: { 1: undefined } undefined undefined
D: undefined undefined undefined
Hàm myFunc
mong đợi một đối tượng có các thuộc tính x
, y
và z
là đối số của nó. Vì chúng ta chỉ chuyển ba giá trị số riêng biệt (1, 2, 3)
thay vì một đối tượng có thuộc tính x
, y
và z
({x: 1, y: 2, z: 3})
, x
, y
và z
có giá trị mặc định của chúng là không xác định.
111. Công dụng của Intl.NumberFormat
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
Với phương thức Intl.NumberFormat
, chúng ta có thể định dạng các giá trị số thành bất kỳ ngôn ngữ nào. Chúng ta định dạng giá trị số 130
cho ngôn ngữ en-US
dưới dạng đơn vị tính bằng mph
, kết quả là 130 miles/h
. Giá trị số 300
cho ngôn ngữ en-US
dưới dạng đơn vị tiền tệ là USD dẫn đến $300,00
.
112. Thêm một phần tử vào mảng bằng destructing function
const spookyItems = ['👻', '🎃', '🕸']; ({ item: spookyItems[3] } = { item: '💀' }); console.log(spookyItems);
A: ["👻", "🎃", "🕸"]
B: ["👻", "🎃", "🕸", "💀"]
C: ["👻", "🎃", "🕸", { item: "💀" }]
D: ["👻", "🎃", "🕸", "[object Object]"]
Bằng cách destructing lại các đối tượng, chúng ta có thể giải nén các giá trị từ đối tượng bên phải và gán giá trị đã giải nén cho giá trị của cùng một tên thuộc tính trên đối tượng bên trái. Trong trường hợp này, chúng ta đang chỉ định giá trị "💀"
đến spookyItems[3]
. Điều này có nghĩa là chúng ta đang sửa đổi mảng spookyItems
, chúng ta đang thêm "💀" vào nó. Khi log spookyItems
, kết quả sẽ là ["👻", "🎃", "🕸", "💀"]
.
113. Sử dụng flat
để duỗi mảng
const emojis = ['🥑', ['✨', '✨', ['🍕', '🍕']]]; console.log(emojis.flat(1));
A: ['🥑', ['✨', '✨', ['🍕', '🍕']]]
B: ['🥑', '✨', '✨', ['🍕', '🍕']]
C: ['🥑', ['✨', '✨', '🍕', '🍕']]
D: ['🥑', '✨', '✨', '🍕', '🍕']
Với phương thức flat
, chúng ta có thể tạo một mảng mới, được duỗi bớt các mảng lồng nhau. Độ sâu của mảng phẳng phụ thuộc vào giá trị mà chúng ta truyền vào. Trong trường hợp này, chúng ta đã truyền giá trị 1
(mà chúng tôi không cần phải làm vậy, đó là giá trị mặc định), nghĩa là chỉ các mảng ở độ sâu đầu tiên sẽ được nối với nhau. ['🥑']
và ['✨', '✨', ['🍕', '🍕']]
trong trường hợp này. Kết hợp hai mảng này sẽ tạo ra ['🥑', '✨', '✨', ['🍕', '🍕']]
114. Proxy hoạt động như thế nào?
const handler = { set: () => console.log('Added a new property!'), get: () => console.log('Accessed a property!'), }; const person = new Proxy({}, handler); person.name = 'Lydia'; person.name;
A: Added a new property!
B: Accessed a property!
C: Added a new property! Accessed a property!
D: Nothing gets logged
Với một đối tượng Proxy
, chúng ta có thể thêm hành vi tùy chỉnh vào một đối tượng mà chúng ta truyền cho nó dưới dạng đối số thứ hai. Trong trường hợp này, chúng ta truyền vào handler
có chứa các thuộc tính: set
và get
. set
được gọi khi chúng ta thiết lập giá thuộc tính, get
được gọi khi chúng ta nhận hoặc tiếp cận với giá trị thuộc tính đó.
Tham số đầu tiên là một đối tượng rỗng {}
, là giá trị của person
. Đối với đối tượng này, hành vi tùy chỉnh được chỉ định trong đối tượng trình xử lý sẽ được thêm vào. Nếu chúng ta thêm một thuộc tính vào đối tượng person
, set
sẽ được gọi. Nếu chúng ta truy cập một thuộc tính trên đối tượng person
, thì get
sẽ được gọi.
Đầu tiên, chúng ta đã thêm một tên thuộc tính mới vào đối tượng proxy (person.name = "Lydia")
. set
được gọi và log ra "Added a new property!"
.
Sau đó, chúng ta truy cập một giá trị thuộc tính trên đối tượng proxy
, thuộc tính get
trên đối tượng handler
được gọi. "Accessed a property!"
115. Phương thức nào sau đây giúp thay đổi thuộc tính trong Object.seal
const person = { name: 'Lydia Hallie' }; Object.seal(person);
A: person.name = "Evan Bacon"
B: person.age = 21
C: delete person.name
D: Object.assign(person, { age: 21 })
Với Object.seal
, chúng ta có thể ngăn không cho thêm các câu lệnh mới hoặc loại bỏ các thuộc tính hiện tại. Tuy nhiên, bạn vẫn có thể sửa đổi giá trị của các thuộc tính hiện có.
116. private
trong class
class Counter { #number = 10 increment() { this.#number++ } getNum() { return this.#number } } const counter = new Counter() counter.increment() console.log(counter.#number)
A: 10
B: 11
C: undefined
D: SyntaxError
Trong ES2020, chúng ta có thể thêm các biến private trong các lớp bằng cách sử dụng dấu #
. Chúng ta không thể truy cập các biến này bên ngoài lớp. Khi chúng ta cố gắng ghi lại số thứ tự của bộ đếm. #
, Một lỗi Cú pháp sẽ xuất hiện: chúng ta không thể truy cập nó bên ngoài lớp Counter
!
117. Truyền một mảng trong một object làm tham số của một hàm sẽ như thế nào?
const person = { name: 'Lydia Hallie', hobbies: ['coding'], }; function addHobby(hobby, hobbies = person.hobbies) { hobbies.push(hobby); return hobbies; } addHobby('running', []); addHobby('dancing'); addHobby('baking', person.hobbies); console.log(person.hobbies);
A: ["coding"]
B: ["coding", "dancing"]
C: ["coding", "dancing", "baking"]
D: ["coding", "running", "dancing", "baking"]
Hàm addHobbies
nhận 2 tham số, hoppy
và hobbies
với giá trị mặc định là mảng hobbies
trong object person
.
Đầu tiên, chúng ta gọi hàm addHobby
và truyền "running"
làm giá trị cho hobby
và một mảng trống làm giá trị cho hobbies
. Vì chúng ta chuyển một mảng trống làm giá trị cho y
, nên "running"
sẽ được thêm vào mảng trống này.
Sau đó, chúng ta gọi hàm addHobby
và truyền "dancing"
làm giá trị cho hobby
. Chúng ta đã không truyền một giá trị cho hobbies
, vì vậy nó nhận giá trị mặc định, là thuộc tính hobbies
trên đối person
. Lúc này, chúng ta đã đưa "dancing"
vào trong mảng person.hobbies
.
Cuối cùng, chúng ta gọi hàm addHobby
và truyền "baking"
làm giá trị cho hobby
và mảng person.hobbies
làm giá trị cho hobbies
. Lúc này, chúng ta đã đưa "baking"
vào trong mảng person.hobbies
Sau khi đưa "dancing"
và "baking"
vào trong person.hobbies
, cuối cùng mảng person.hobbies
sẽ có giá trị là ["coding", "dancing", "baking"]
118. Toán tử ?.
function getFruit(fruits) { console.log(fruits?.[1]?.[1]) } getFruit([['🍊', '🍌'], ['🍍']]) getFruit() getFruit([['🍍'], ['🍊', '🍌']])
A: null, undefined, 🍌
B: [], null, 🍌
C: [], [], 🍌
D: undefined, undefined, 🍌
Toán tử ?
cho phép chúng ta có thể truy cập các phần tử sâu bên trong các mảng lồng nhau. Chúng ta đang cố gắng truy cập đến phần tử thứ 1 trong mảng con nằm trong phần tử thứ 1 của mảng cha là fruits
. Nếu mảng con trên index thứ 1 trong mảng fruits
không tồn tại, nó sẽ chỉ trả về undefined
. Nếu mảng con trên index thứ 1 trong mảng fruits
tồn tại, nhưng mảng con này chỉ có 1 phần tử trong mảng (tức là chỉ có index 0) của nó, nó cũng sẽ trả về undefined
Đầu tiên, chúng ta cố gắng truy cập vào index số 1 trong mảng con ['🍍']
của [['🍊', '🍌'], ['🍍']]
. Mảng con này chỉ chứa một index (0), có nghĩa là không có mục nào trên chỉ mục là 1 và do đó nó trả về undefined
. Sau đó, chúng ta đang gọi hàm getFruits
mà không chuyển một giá trị làm tham số, có nghĩa là các fruits
có giá trị undefined
theo mặc định. Vì chúng ta xâu chuỗi mục theo điều kiện trên các fruits
với index 1, nó trả về undefined
vì trên mảng này index 1 không tồn tại.
Cuối cùng, chúng ta log index thứ 1 trong mảng con ['🍊', '🍌']
của ['🍍'], ['🍊', '🍌']
. Index thứ 1 trong mảng con này là 🍌
, do đó, no được log ra.
119. Hai object dưới đây có bằng nhau không?
const user = { email: "e@mail.com", password: "12345" } const updateUser = ({ email, password }) => { if (email) { Object.assign(user, { email }) } if (password) { user.password = password } return user } const updatedUser = updateUser({ email: "new@email.com" }) console.log(updatedUser === user)
A: false
B: true
C: TypeError
D: ReferenceError
Hàm updateUser
cập nhật các giá trị của thuộc tính email
và password
trên user
, nếu giá trị của chúng được chuyển cho hàm, sau đó hàm trả về object user
. Giá trị trả về của hàm updateUser lúc này là object user
, có nghĩa là giá trị của updatedUser
là một tham chiếu đến cùng một đối tượng mà user
trỏ đến. Do đó, updatedUser === user
là true
.
120. slice
, slpice
, unshift
trong mảng
const fruit = ['🍌', '🍊', '🍎'] fruit.slice(0, 1) fruit.splice(0, 1) fruit.unshift('🍇') console.log(fruit)
A: ['🍌', '🍊', '🍎']
B: ['🍊', '🍎']
C: ['🍇', '🍊', '🍎']
D: ['🍇', '🍌', '🍊', '🍎']
Đầu tiên, chúng ta gọi phương thức slice
trên mảng fruit
. Phương thức slice
không sửa đổi mảng ban đầu, nhưng trả về những giá trị nào mà nó cắt ra khỏi mảng: đó chính là "🍌"
. Sau đó, chúng ta gọi phương splice
trên mảng quả. Phương thức splice
không sửa đổi mảng ban đầu, có nghĩa là mảng fruit
bây giờ bao gồm ['🍊', '🍎']
. Cuối cùng, chúng ta gọi phương thức unshift
trên mảng fruit
, phương thức này sẽ sửa đổi mảng ban đầu bằng cách thêm giá trị đã cho.
"🍇"
được đưa vào phần tử đầu tiên trong trường hợp này. Mảng fruit
bây giờ bao gồm ['🍇', '🍊', '🍎']
.
121. thuộc tính arrow function
trong object
const user = { email: "my@email.com", updateEmail: email => { this.email = email } } user.updateEmail("new@email.com") console.log(user.email)
A: my@email.com
B: new@email.com
C: undefined
D: ReferenceError
Hàm updateEmail
là một hàm arrow function và không bị ràng buộc với đối tượng user
. Điều này có nghĩa là từ khóa this
không đề cập đến đối tượng user
, nhưng đề cập đến phạm vi global scope
trong trường hợp này. Giá trị của email
trong đối tượng user
không được cập nhật. Khi ghi lại giá trị của user.email
, giá trị ban đầu của my@email.com
sẽ được trả lại.
122. Promise all
khi gặp catch
const promise1 = Promise.resolve('First') const promise2 = Promise.resolve('Second') const promise3 = Promise.reject('Third') const promise4 = Promise.resolve('Fourth') const runPromises = async () => { const res1 = await Promise.all([promise1, promise2]) const res2 = await Promise.all([promise3, promise4]) return [res1, res2] } runPromises() .then(res => console.log(res)) .catch(err => console.log(err))
A: [['First', 'Second'], ['Fourth']]
B: [['First', 'Second'], ['Third', 'Fourth']]
C: [['First', 'Second']]
D: 'Third'
Phương thức Promise.all
chạy song song với các Promise
đã truyền. Nếu một Promise
bị reject
, phương thức Promise.all
sẽ reject
với giá trị của của Promise đã bị reject
. Trong trường hợp này, Promise
bị từ chối với giá trị "Third"
. Chúng ta đang bắt giá trị bị từ chối trong phương thức bắt chuỗi trên lệnh gọi runPromises
để bắt bất kỳ lỗi nào trong hàm runPromises. Chỉ "Third"
mới được log ra, vì 3 Promise.resolve
kia bị từ chối bởi giá trị này.
123. Lưu ý !typeof
let randomValue = { name: "Lydia" } randomValue = 23 if (!typeof randomValue === "string") { console.log("It's not a string!") } else { console.log("Yay it's a string!") }
A: It's not a string!
B: Yay it's a string!
C: TypeError
D: undefined
Điều kiện trong câu lệnh if
kiểm tra xem giá trị của !typeof randomValue
có bằng "string"
hay không. Với toán tử !
nó giúp chuyển đổi giá trị thành giá trị boolean
. Nếu giá trị là true
, thì với !
nó sẽ giá trị trả về sẽ là false
, ngược lại nếu giá trị là false
, thì giá trị trả về sẽ là true
. Trong trường hợp này, giá trị trả về của typeof randomValue
là giá trị true "number"
, có nghĩa là giá trị của !typeof randomValue
là giá trị boolean false
.
Tuy nhiên, !typeof randomValue === "string"
luôn trả về false
, vì chúng ta đang thực sự kiểm tra false === "string"
. Vì điều kiện trả về false
, khối mã của câu lệnh else
sẽ được chạy và "Yay it's a string!"
sẽ được log ra.
Series Những câu hỏi hay trong Javascript đến đây là hết, thực sự nó cũng khá dài và khó vì có những thứ trong thực tế chúng ta cũng chưa bao giờ gặp nó. Qua đây, hy vọng các bạn sẽ khám phá ra được nhiều điều mới mẻ trong javascript để có thể đào sâu hơn về nó, tận dụng được những điểm mạnh và tránh được những lỗi không đáng có khi lập trình với JS. Cám ơn các bạn đã quan tâm theo dõi, hẹn gặp lại các bạn trong những chủ để tiếp theo.