Phương thức của Object và this trong JavaScript
Như mình đã đề cập trong bài viết đầu tiên về object, object trong JavaScript dùng để biểu diễn một đối tượng cụ thể. Mà đối tượng không chỉ có các thuộc tính, đối tượng còn có hành động.
Sau đây, mình sẽ tìm hiểu về phương thức của object và một từ khóa quan trọng, đó là this
trong JavaScript.
Phương thức của Object
Trong JavaScript, hành động của object được biểu diễn bởi hàm. Ví dụ đối tượng user
với hành động sayHello()
như sau:
let user = {
name: "Alex",
age: "28",
};
user.sayHello = function () { console.log("Hello!");};
user.sayHello(); // Hello!
Trong ví dụ trên, mình sử dụng function expression để tạo một hàm, rồi gán hàm đó cho thuộc tính sayHello
của user
.
Tiếp theo, mình gọi user.sayHello()
. Và kết quả là Hello!
được hiển thị ra console.
📝 Một hàm là thuộc tính của object thì nó được gọi là phương thức.
Vì vậy, sayHello
chính là một phương thức của object user
.
Ngoài cách sử dụng function expression như trên, bạn có thể dùng function declaration để khai báo hàm như sau:
let user = {
name: "Alex",
age: "28",
};
function sayHello() { console.log("Hello!");};user.sayHello = sayHello;
user.sayHello(); // Hello!
💡 Cách sử dụng object để biểu diễn đối tượng với các thuộc tính và hành động như trên gọi là lập trình hướng đối tượng hay OOP.
Cú pháp ngắn gọn khai báo phương thức
Trong các ví dụ trên, mình khởi tạo object xong rồi mới định nghĩa thêm phương thức. Bạn cũng có thể định nghĩa phương thức từ khi khởi tạo object.
Ví dụ khởi tạo object với phương thức:
let user = {
name: "Alex",
age: "28",
sayHello: function () { console.log("Hello!"); },};
user.sayHello(); // Hello!
Ngoài ra, bạn cũng có thể bỏ qua từ khóa function
như sau:
let user = {
name: "Alex",
age: "28",
sayHello() { console.log("Hello!"); },};
user.sayHello(); // Hello!
Trên đây là một số kiến thức cơ bản về phương thức của object. Sau đây, mình tiếp tục tìm hiểu về từ khóa this
trong JavaScript.
Từ khóa this trong JavaScript
Một trường hợp khá phổ biến đối với object là việc truy cập vào thuộc tính của object ngay bên trong phương thức.
Ví dụ phương thức sayHello
trên muốn truy cập và hiển thị giá trị của thuộc tính name
. Để làm được điều này, bạn có thể sử dụng từ khóa this
.
Giá trị của this
trong JavaScript chính là object gọi phương thức - đối tượng trước dấu chấm (.
), ví dụ:
let user = {
name: "Alex",
age: "28",
sayHello() {
console.log(this.name + " says Hello!"); },
};
user.sayHello(); // Alex says Hello!
Khi chương trình thực thi, giá trị của this
chính là user
. Hay nói cách khác this.name
chính là user.name
.
Vậy tại sao không sử dụng trực tiếp user
bên trong phương thức sayHello
?
Ví dụ sử dụng trực tiếp user
:
let user = {
name: "Alex",
age: "28",
sayHello() {
console.log(user.name + " says Hello!"); },
};
user.sayHello(); // Alex says Hello!
Kết quả vẫn đúng. Nhưng liệu vấn đề gì có thể xảy ra?
Giả sử, bạn muốn copy object dạng tham chiếu từ user
sang admin
rồi ghi đè giá trị của user
:
let user = {
name: "Alex",
age: "28",
sayHello() {
console.log(user.name + " says Hello!");
},
};
let admin = user;user = null;admin.sayHello();// Uncaught TypeError: Cannot read properties of null (reading 'name')
Câu lệnh cuối cùng bị lỗi.
Vì khi bạn gọi admin.sayHello()
, bên trong phương thức sayHello
đang gọi user.name
. Mà giá trị user
đã bị gán bằng null
.
Kết quả, bạn bị lỗi Uncaught TypeError: Cannot read properties of null (reading 'name').
Nếu bạn dùng
this.name
thì sẽ không bị lỗi trên. Vìthis
lúc này được hiểu làadmin
.
This trong JavaScript không được "bind"
Khác với các ngôn ngữ lập trình khác, từ khóa this
có thể dùng ở bất kỳ hàm nào (không chỉ là phương thức của object),.
Ví dụ sau đây không bị lỗi cú pháp:
function sayHello() {
console.log(this.name);}
Giá trị của this
được xác định trong thời gian chạy, phụ thuộc vào đối tượng gọi hàm, ví dụ:
let user1 = { name: "Alex" };
let user2 = { name: "John" };
function sayHello() {
console.log(this.name);
}
// Sử dụng cùng 1 hàm cho 2 objects
user1.sayHi = sayHello;user2.sayHi = sayHello;user1.sayHi(); // Alex (this tương ứng với user1)user2.sayHi(); // John (this tương ứng với user2)
Bạn thấy rằng, tùy thuộc vào đối tượng gọi hàm là user1
hay user2
mà giá trị của this
được xác định tương ứng.
Điều này giải thích lý do tại sao this trong JavaScript không được "bind".
Nếu hàm sayHello
trên được gọi trực tiếp không qua object nào thì sao?
Ví dụ gọi trực tiếp sayHello()
:
"use strict";
function sayHello() {
console.log(this);
}
sayHello(); // undefined
Trong trường hợp này, giá trị của this
là undefined
ở strict mode. Nếu bạn truy cập this.name
thì sẽ bị lỗi Uncaught TypeError: Cannot read properties of undefined (reading 'name').
"use strict";
function sayHello() {
console.log(this.name);}
sayHello();
// Uncaught TypeError: Cannot read properties of undefined (reading 'name')
Nếu không sử dụng strict mode thì giá trị của this
sẽ là đối tượng global (đối tượng window
trên trình duyệt).
Khi đó, this.name
tương đương với window.name
(thường là undefined
).
Arrow function không có this
Arrow function là một hàm đặc biệt, vì nó không có this
.
Nếu bạn truy cập this
bên trong arrow function thì JavaScript sẽ hiểu this
là đối tượng ứng với ngữ cảnh gần nhất bên ngoài có this
lúc gọi hàm.
Ví dụ dùng arrow function:
let user = {
name: "Alex",
age: "28",
sayHello() {
let arrowFunc = () => console.log(this.name); arrowFunc(); },
};
user.sayHello(); // Alex
Trong ví dụ trên, ngữ cảnh gần nhất bên ngoài có this
là phương thức sayHello
. Mà trong phương thức sayHello
, giá trị của this
được xác định lúc gọi user.sayHello()
.
Do đó, this
chính là user
. Và kết quả là this.name
bằng user.name
.
Nếu phương thức sayHello
được gán trực tiếp bằng arrow function thì sao?
Ví dụ:
let user = {
name: "Alex",
age: "28",
sayHello: () => console.log(this.name),};
user.sayHello(); // undefined
Lúc này, ngữ cảnh gần nhất có this
lúc gọi hàm là global. Trong trường hợp không dùng strict mode thì this
chính là window
. Vì vậy, this.name
bằng undefined
.
Arrow function có nhiều vấn đề khác nữa cần quan tâm. Bạn có thể tham khảo bài phân biệt arrow function và function để hiểu thêm về arrow function.
Tổng kết
Sau đây là một số kiến thức cần nhớ về phương thức của object và từ khóa this
trong JavaScript:
- Hàm được gán cho thuộc tính của object gọi là phương thức.
- Trong phương thức, bạn có thể truy cập đến các thuộc tính khác của object thông qua từ khóa
this
.
Từ khóa this
trong JavaScript được xác định khi chạy chương trình:
- Khi định nghĩa hàm,
this
có thể được sử dụng. Nhưng lúc nàythis
chưa có giá trị, cho đến khi hàm được gọi. - Một hàm có thể được sử dụng trong nhiều object.
- Khi một hàm được gọi thông qua object dạng
object.method()
thì giá trị củathis
chính làobject
.
Đặc biệt, arrow function không có this
. Giá trị của this
trong arrow function được lấy từ ngữ cảnh gần nhất bên ngoài lúc gọi hàm.
Thực hành
Bài 1
Cho đoạn code sau:
"use strict";
function createUser(name) {
return {
name,
ref: this,
};
}
let alex = createUser("Alex");
console.log(alex.ref.name);
Kết quả của console.log
là gì?
Kết quả: Uncaught TypeError: Cannot read properties of undefined (reading 'name').
Giải thích:
- Vì giá trị của
this
được xác định tại thời điểm thực thi chương trình. - Mà hàm
createUser
được gọi như một hàm bình thường, không thông qua object nào cả. Do đó, giá trị củathis
làundefined
. Nói cách khác,user.ref
trả vềundefined
. - Trong strict mode, truy cập vào thuộc tính của
undefined
sẽ bị lỗi Uncaught TypeError: Cannot read properties of undefined (reading 'name').
Bài 2
Triển khai 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.
let calculator = {
// viết code trong đây
};
calculator.read();
console.log(calculator.sum());
console.log(calculator.mul());
let calculator = {
// Phương thức read()
read() {
this.a = +prompt("Nhập vào số a:", 0);
this.b = +prompt("Nhập vào số b:", 0);
},
// Phương thức add()
add() {
return this.a + this.b;
},
// Phương thức mul()
mul() {
return this.a * this.b;
},
};
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
Cho đoạn code sau:
// Khởi tạo obj
let obj = {
count: 0,
increase() {
this.count++;
},
decrease() {
this.count--;
},
showCount() {
console.log(this.count);
},
};
// Sử dụng obj
obj.increase();
obj.increase();
obj.decrease();
obj.showCount(); // 1
Hãy sửa lại các phương thức của obj
để có thể gọi code theo kiểu:
obj.increase().increase().decrease().showCount(); // 1
Kĩ thuật này gọi là Method chaining.
Ý tưởng là: trong mỗi phương thức, bạn sẽ return
về this
(đối tượng hiện tại).
// Khởi tạo obj
let obj = {
count: 0,
increase() {
this.count++;
return this; },
decrease() {
this.count--;
return this; },
showCount() {
console.log(this.count);
return this; },
};
// Sử dụng obj
obj.increase().increase().decrease().showCount(); // 1
Bạn hiểu là increase()
, decrease()
hay showCount()
đều trả về this
- chính là obj
.
★ Nếu bạn thấy bài viết này hay thì hãy theo dõi mình trên Facebook để nhận được thông báo khi có bài viết mới nhất nhé:
- Facebook Fanpage: Complete JavaScript
- Facebook Group: Hỏi đáp JavaScript VN
Bình luận