Trong các bài viết trước, mình chủ yếu dùng cú pháp {...}
để khởi tạo object. Tuy nhiên, cách này chỉ dùng để khởi tạo một object riêng lẻ.
Nếu mình muốn khởi tạo nhiều object tương tự nhau thì sao?
Để giải quyết vấn đề này, bạn có thể sử dụng toán tử new
trong JavaScript kết hợp với một hàm khởi tạo.
Tóm Tắt
Hàm khởi tạo và new trong JavaScript là gì?
Hàm khởi tạo về bản chất là một hàm bình thường, nhưng dùng để khởi tạo object.
Một số đặc điểm của hàm khởi tạo là:
- Hàm khởi tạo thường được viết hoa chữ cái đầu (không bắt buộc) để dễ dàng phân biệt với các hàm bình thường.
- Hàm khởi tạo chỉ nên sử dụng với toán tử
new
trong JavaScript.
Ví dụ:
function
Point
(
x
,
y)
{
this
.
x
=
x;
this
.
y
=
y;
}
let
root =
new
Point
(
0
,
0
)
;
console
.
log
(
root.
x
,
root.
y
)
;
Khi một hàm được gọi với toán tử new
, JavaScript Engine sẽ xử lý các bước như sau:
- Khởi tạo object rỗng và gán cho
this
. - Các câu lệnh trong thân hàm được thực thi, thường là cập nhật
this
hoặc thêm các thuộc tính chothis
. - Trả về giá trị của
this
.
Nói cách khác, cú pháp new Point(...)
thực hiện:
function
Point
(
x
,
y)
{
this
.
x
=
x;
this
.
y
=
y;
}
Như vậy, new Point(0,0)
tương đương với cách khởi tạo object là:
let
root =
{
x
:
0
,
y
:
0
,
}
;
Bây giờ, nếu bạn muốn tạo ra các point khác, bạn chỉ cần gọi new p1(1, 2)
, new p2(2, 3)
,… thay vì phải sử dụng cú pháp {...}
nhiều lần (và dài dòng hơn).
📝 Chú ý:
Mục đích chính của hàm khởi tạo là để dễ dàng tái sử dụng code.
Arrow function không có this
nên không được dùng làm hàm khởi tạo.
Nếu hàm khởi tạo không có tham số thì bạn có thể bỏ qua cặp dấu ngoặc đơn ()
, ví dụ:
function
Point
(
)
{
this
.
x
=
0
;
this
.
y
=
0
;
}
let
root =
new
Point
(
)
;
console
.
log
(
root.
x
,
root.
y
)
;
Tuy nhiên, mình khuyên bạn nên sử dụng cách gọi hàm khởi tạo với cặp dấu ng
()
, vì nó chuẩn hơn và đúng với cú pháp gọi hàm.
Bạn có thể khai báo, đồng thời khởi tạo object ngay với cú pháp new function(){...}
như sau:
let
root =
new
(
function
(
)
{
this
.
x
=
1
;
this
.
y
=
2
;
}
)
(
)
;
console
.
log
(
root.
x
,
root.
y
)
;
Cú pháp này gọi là IFFE.
Khi tạo object theo cách này, hàm khởi tạo sẽ chỉ được gọi một lần (vì bản chất hàm khởi tạo không được lưu vào biến nào).
Vì vậy, mục đích của cách khai báo này không phải để tái sử dụng, mà để đóng gói code liên quan trong một hàm khởi tạo.
Kiểm tra hàm khởi tạo được gọi với new trong JavaScript
Để kiểm tra hàm khởi tạo có được gọi với new trong JavaScript hay không, bạn sử dụng thuộc tính đặc biệt là new.target
.
Nếu hàm được gọi theo cách thông thường thì new.target
sẽ bằng undefined
, ngược lại new.target
bằng chính function:
function
Point
(
)
{
console
.
log
(
new
.
target
)
;
}
Point
(
)
;
new
Point
(
)
;
Thuộc tính đặc biệt này có thể được áp dụng để kiểm tra xem hàm khởi tạo có được gọi với new
hay không.
Trường hợp hàm khởi tạo không được gọi với new
, mình có thể xử lý thêm để trả về giống cách gọi hàm với new
:
function
Point
(
x
,
y)
{
if
(
!
new
.
target
)
{
return
new
Point
(
x,
y)
;
}
this
.
x
=
x;
this
.
y
=
y;
}
let
root =
Point
(
0
,
0
)
;
console
.
log
(
root.
x
,
root.
y
)
;
Với cách viết như này, bạn có thể khởi tạo object với new
hoặc không có new
thì đều cho kết quả giống nhau.
💡 Chú ý:
new.target
ít được sử dụng trong thực tế.
Việc khởi tạo object nên luôn luôn sử dụng từ khóa new
để đảm bảo code rõ ràng và dễ hiểu nhất.
Trả về giá trị từ hàm khởi tạo
Thông thường, hàm khởi tạo không có từ khóa return
vì JavaScript Engine ngầm định sẽ trả về this
. Tuy nhiên, bạn có thể sử dụng return
trong hàm khởi tạo với quy tắc như sau:
- Nếu
return
được gọi với object thì giá trị trả về của hàm khởi tạo là object chứ không phảithis
. - Nếu
return
được gọi với giá trị nguyên thủy,return
sẽ bị bỏ qua.
Nói cách khác, return
với một object sẽ trả về object đó, ngược lại thì trả về this
.
Ví dụ hàm khởi tạo trả về một object khác this
:
function
Point
(
x
,
y)
{
this
.
x
=
x;
this
.
y
=
y;
return
{
x
:
100
,
y
:
100
}
;
}
let
p =
new
Point
(
0
,
0
)
;
console
.
log
(
p.
x
,
p.
y
)
;
Ví dụ hàm khởi tạo trả về giá trị nguyên thủy:
function
Point
(
x
,
y)
{
this
.
x
=
x;
this
.
y
=
y;
return
1
;
}
let
p =
new
Point
(
0
,
0
)
;
console
.
log
(
p.
x
,
p.
y
)
;
Định nghĩa phương thức trong hàm khởi tạo
Object không chỉ có thuộc tính mà còn có cả phương thức.
Và dĩ nhiên, bạn có thể định nghĩa phương thức trong hàm khởi tạo của object, ví dụ:
function
Point
(
x
,
y)
{
this
.
x
=
x;
this
.
y
=
y;
this
.
printLog
=
function
(
)
{
console
.
log
(
this
.
x
,
this
.
y
)
;
}
;
}
let
root =
new
Point
(
0
,
0
)
;
root.
printLog
(
)
;
Để tạo nhiều object phức tạp hơn, bạn có thể sử dụng cú pháp nâng cao hơn như prototype hay class (sẽ được giới thiệu sau).
Tổng kết
Sau đây là những kiến thức cơ bản cần nhớ về khởi tạo object với toán tử new
trong JavaScript:
- Hàm khởi tạo là một hàm thông thường nhưng được dùng để khởi tạo object.
- Hàm khởi tạo thường viết hoa chữ cái đầu tiên.
- Hàm khởi tạo chỉ nên sử dụng với toán tử
new
. Khi đó, JavaScript ngầm định tạo ra một object rỗng ở đầu hàm và gán chothis
. Sau đó, cuối hàm sẽreturn
vềthis
. - Arrow function không có
this
nên không được dùng làm hàm khởi tạo. - Bạn có thể khai báo và gọi hàm ngay lập tức với cú pháp IFFE
new function(){...}
- Thường hàm khởi tạo không có
return
. Nếu hàm khởi tạo córeturn
thì quy tắc là:return
với một object sẽ trả về object đó, ngược lại thì trả vềthis
. - Bạn có thể định nghĩa phương thức trong hàm khởi tạo.
Thực hành
Bài 1
Cho đoạn code sau:
function
A
(
)
{
...
}
function
B
(
)
{
...
}
let
a =
new
A
;
let
b =
new
B
;
console
.
log
(
a ===
b)
;
Có cách nào để tạo hàm A
và B
sao cho new A() === new B()
?
Xem đáp án
Đáp án là: Có.
Để new A() === new B()
là true
thì hàm khởi tạo A
và B
phải trả về cùng một object.
let
obj =
{
}
;
function
A
(
)
{
return
obj;
}
function
B
(
)
{
return
obj;
}
let
a =
new
A
(
)
;
let
b =
new
B
(
)
;
console
.
log
(
a ===
b)
;
Bài 2
Viết hàm khởi tạo object Calculator
với ba phương thức:
read()
: sử dụng hàmprompt
đọc hai giá trị và lưu vào hai thuộc tính của object (giả sử người dùng nhập vào là số).add()
: trả về tổng của hai số đã nhập.mul()
: trả về tích của hai số đã nhập.
Ví dụ:
let
calculator =
new
Calculator
(
)
;
calculator.
read
(
)
;
console
.
log
(
calculator.
sum
(
)
)
;
console
.
log
(
calculator.
mul
(
)
)
;
Xem đáp án
function
Calculator
(
)
{
this
.
read
=
function
(
)
{
this
.
a
=
+
prompt
(
"Nhập vào số a:"
,
0
)
;
this
.
b
=
+
prompt
(
"Nhập vào số b:"
,
0
)
;
}
;
this
.
add
=
function
(
)
{
return
this
.
a
+
this
.
b
;
}
;
this
.
mul
=
function
(
)
{
return
this
.
a
*
this
.
b
;
}
;
}
let
calculator =
new
Calculator
(
)
;
calculator.
read
(
)
;
console
.
log
(
calculator.
add
(
)
)
;
console
.
log
(
calculator.
mul
(
)
)
;
Chú ý: hàm prompt
trả về kết quả là string. Vì vậy, mình thêm toán tử +
đằng trước để chuyển đổi kiểu dữ liệu về number, trước khi gán cho this.a
và this.b
.
Bài 3
Viết hàm khởi tạo Counter(startValue)
(giả sử startValue
là số).
Object tạo ra có những đặc điểm sau:
- Giá trị
startValue
được lưu vào thuộc tínhvalue
. - Phương thức
read()
sử dụng hàmprompt()
để yêu cầu người dùng nhập vào một số. Sau đó, giá trị số người dùng nhập sẽ được cộng dồn vào thuộc tínhvalue
.
Ví dụ:
let
counter =
new
Counter
(
1
)
;
counter.
read
(
)
;
counter.
read
(
)
;
console
.
log
(
counter.
value
)
;
Xem đáp án
function
Counter
(
startValue
)
{
this
.
value
=
startValue;
this
.
read
=
function
(
)
{
this
.
value
+=
+
prompt
(
"Nhập vào một số:"
,
0
)
;
}
;
}
let
counter =
new
Counter
(
1
)
;
counter.
read
(
)
;
counter.
read
(
)
;
console
.
log
(
counter.
value
)
;