Khởi tạo đối tượng với từ khóa new trong javascript có tác dụng gì

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.

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:

  1. Khởi tạo object rỗng và gán cho this.
  2. 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 cho this.
  3. 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ải this.
  • 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 cho this. 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 AB sao cho new A() === new B()?

Xem đáp án

Đáp án là: .

Để new A() === new B()true thì hàm khởi tạo AB 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àm prompt đọ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.athis.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ính value.
  • Phương thức read() sử dụng hàm prompt() để 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ính value.

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

)

;