Nội dung bài viết
Object thường được tạo ra để đại diện cho các thực thể của thế giới thực, như người dùng, đơn hàng, v.v.
let user = { name: "John", age: 30 };
Và trong thế giới thực thì một user có thể hành động: chọn thứ gì đó từ giỏ hàng, login, logout,…
Các hành động thì được đại diện bằng Javascript function, cụ thể hơn là các function trong các thuộc tính.
Ví dụ Method (phương thức)
Để bắt đầu, chúng ta cùng dạy cho user
nói hello:
let user = { name: "John", age: 30 }; user.sayHi = function() { alert("Hello!"); }; user.sayHi(); // Hello!
Ở đây chúng ta sử dụng một Function Expression để tạo một function và gán nó vào thuộc tính user.sayHi
của object.
Sau đó chúng ta có thể gọi nó bằng cách user.sayHi()
. Bây giờ user có thể nói!
Một function là thuộc tính của object thì được gọi là method (phương thức)
Vì thế, chúng ta được method sayHi
của object user
Tất nhiên là chúng ta cũng có thể sử dụng một function đã được khai báo từ trước như thế này:
let user = { // ... }; // first, declare function sayHi() { alert("Hello!"); }; // Sau đó thêm một method user.sayHi = sayHi; user.sayHi(); // Hello!
Object-oriented programming – Lập trình hướng đối tượng
Khi chúng ta viết code sử dụng object (đối tượng) để đại diện cho các thực thể, chúng được gọi là lập trình hướng đối tượng, gắn gọn là “OOP”.
OOP là một điều to lớn, và nó có sự thú vị của riêng nó. Cách đẻ đúng thực thể? Cách để tổ chức và tương tác giữa các object? Những thứ đó được gọi là kiến trúc, và có một số quyển sách về chủ đề này mà mình recommend mọi người đọc như Design Patterns: Elements of Reusable Object-Oriented Software” của E. Gamma, R. Helm, R. Johnson, J. Vissides hoặc “Object-Oriented Analysis and Design with Applications” của G. Booch,…
Method shorthand
Có một cách viết ngắn gọn cho method trong object literal:
user = { sayHi: function() { alert("Hello"); } }; // method bây giờ nhìn xịn hơn phải không nào? user = { sayHi() { // tương tự "sayHi: function()" alert("Hello"); } };
Như đã viết bên trên thì chúng ta có thể bỏ qua "function"
và chỉ cần viết sayHi()
.
Thành thật mà nói thì 2 cách trên không hoàn toàn là giống nhau đâu. Có những khác biệt nhỏ liên quan đến kế thừa đối tượng, nhưng hiện tại chúng không quan trọng lắm. Trong hầu hết các trường hợp thì cú pháp ngắn gọn được ưu tiên hơn.
“this” trong method
Có một điều phổ biến là object method cần truy cập thông tin được lưu trong object để thực hiện một công việc gì đó.
Ví dụ, code bên trong user.sayHi()
cần name của user
.
Để truy cập object, một method có thể sử dụng từ khóa this.
Giá trị của this
là object “trước dấu chấm”, chính là object mà dùng để gọi method.
Ví dụ:
let user = { name: "John", age: 30, sayHi() { // "this" là object hiện tại alert(this.name); } }; user.sayHi(); // John
Ở đây trong suốt quá trình thực thi user.sayHi()
, giá trị của this
sẽ là user
.
Về mặt kỹ thuật, có thể truy cập đến object mà không cần this
, bằng cách tham chiếu trực tiếp đến biến.
let user = { name: "John", age: 30, sayHi() { alert(user.name); // "user" thay vì "this" } }
Nhưng code như vậy thì không đáng tin cậy cho lắm. Nếu chúng ta quyết định copy user
sang một biến khác, ví dụ admin = user
và sau đó ghi đè user
, như vậy thì nó sẽ truy cập đến một object sai.
Điều này được minh họa bên dưới:
let user = { name: "John", age: 30, sayHi() { alert( user.name ); // Điều này sẽ dẫn đến 1 lỗi } }; let admin = user; user = null; // ghi đè để làm thứ gì đó admin.sayHi(); // TypeError: Cannot read property 'name' of null
Nếu chúng ta tạo this.name
thay vì user.name
bên trong alert
thì code sẽ hoạt động đúng.
“this” không bị ràng buộc
Trong javascript, từ khóa this
không hoạt động giống như hầu hết các ngôn ngữ lập trình khác. Nó có thể được sử dụng bên trong bất kỳ function nào, ngay cả khi nó không phải là một method của một object
Không có lỗi nào xảy ra trong đoạn code dưới đây:
function sayHi() { alert( this.name ); } sayHi()
Lưu ý ở đây chúng ta chạy trong non-strict mode thì this lúc này là global object (như Window), nếu bạn ở strict mode thì sẽ gặp lỗi vì
this
làundefined
.
'use strict' function sayHi() { alert(this.name) // Cannot read property 'name' of undefined } sayHi()
Giá trị của this
được tính toán suốt quá trình chạy code, dựa vào ngữ cảnh của nó.
Ví dụ, đây là cùng một function được gán cho 2 object khác nhau và nó this khác nhau khi gọi:
let user = { name: "John" }; let admin = { name: "Admin" }; function sayHi() { alert( this.name ); } // sử dụng cùng 1 function trong 2 object user.f = sayHi; admin.f = sayHi; // Chúng có this khác nhau // "this" bên trong function là object trước dấu chấm user.f(); // John (this == user) admin.f(); // Admin (this == admin) admin['f'](); // Admin (dùng dấu chấm hay dấu ngoặc vuông truy cập đến method - không thành vấn đề)
Quy luật đơn giản thôi: nếu obj.f()
được gọi, thì this
là obj
trong suốt quá trình gọi f
. Vì thế nó có thể là user
hoặc admin
ở ví dụ trên.
Hậu quả của việc không ràng buộc
this
Nếu bạn đến từ một ngôn ngữ lập trình khác, thì bạn có thể sẽ quen thuộc ý tưởng “
this
ràng buộc”, nơi mà các method được định nghĩa bên trong object luôn luôn có this tham chiếu đến object đó.Trong Javascript
this
thì “tự do”, giá trị của nó được tính toàn ngay tại lúc gọi và không dựa vào nơi method được khai báo, nhưng đúng hơn là đối tượng “trước dấu chấm”.Concept của việc run-time tính toán
this
có cả ưu điểm và nhược điểm. Một mặt, một chức năng có thể được sử dụng cho các object khác nhau. Mặc khác, tính linh hoạt cao hơn tạo ra nhiều khả năng mắc sai lầm hơn.Ở đây, quan điểm của chúng ta không phải phán xét ngôn ngữ này tốt hay xấu. Cách chúng ta nên tiếp cận là hiểu cách hoạt động của nó, cách để nhận được các lợi ích từ việc
this
không bị ràng buộc và tránh các vấn đề.
Arrow function không có “this”
Arrow function thì đặc biệt: chúng không có this
của nó.
Nếu chúng ta tham chiếu đến this
trong một arrow function, nó sẽ lấy this
bên ngoài nó.
Ví dụ:
'use strict' function handle1() { console.log(this) } const handle2 = () => { console.log(this) } handle1() // undefined handle2() // Window object
Một ví dụ khác, ở đây arrow()
sẽ sử dụng this
bên ngoài arrow()
, tức lúc này là object user
'use strict' let user = { firstName: 'Ilya', sayHi() { let arrow = () => alert(this.firstName) arrow() } } user.sayHi() // Ilya
Đó là tính năng đặc biệt của arrow function, nó hữu ích khi chúng ta tạo thực sự không muốn có một this
riêng biệt.
Nếu không dùng arrow function thì lúc gọi this
sẽ là this
của function arrow()
, mà this trong function arrow()
là undefined
'use strict' let user = { firstName: 'Ilya', sayHi() { function arrow() { alert(this.firstName) // Cannot read property 'firstName' of undefined } arrow() } } user.sayHi()
Tóm lại
- Function được lưu trữ bên trong thuộc tính object được gọi là “method” (phương thức).
- Method cho phép object “hành động” như là
object.doSomething()
. - Method có thể tham chiếu đến object bằng cách dùng
this
.
Giá trị của this
được xác định lúc run-time.
- Khi một function được khai báo, nó có thể sử dụng
this
, nhưngthis
không có giá trị cho đến khi function được gọi. - Một function có thể được copy giữa các object
- Khi một function được gọi theo cú pháp “method”:
object.method()
, giá trị củathis
ở method trong suốt quá trình chạy làobject
.
Hãy lưu ý rằng arrow function không có this
. Khi this
được truy cập bên trong arrow function, nó sẽ được lấy từ bên ngoài.
Bài tập thực hành
1. Sử dụng “this” trong object literal
Ở đây function makeUser
return một object.
Kết quả của việc truy cập đến ref
của nó là gì? Tại sao?
Lưu ý là chạy trong strict mode
function makeUser() { return { name: "John", ref: this }; } let user = makeUser(); alert( user.ref.name ); // What's the result?
Giải pháp
Câu trả lời: một lỗi
Thử lại:
function makeUser() { return { name: "John", ref: this }; } let user = makeUser(); alert( user.ref.name ); // Error: Cannot read property 'name' of undefined
Đó là bởi vì quy tắc thiết lập this
không phụ thuộc vào object khai báo. Chỉ thời điểm gọi mới quan trọng.
Ở đây giá trị của this
bên trong makeUser()
là undefined
, bởi bì nó được gọi như một function, nó không như một method với cú pháp “dấu chấm”.
Giá trị của this đại diện cho this
của cả function, code block và object literal không ảnh hưởng đến nó.
Vậy nên ref: this
nhận lấy this
của function.
Chúng ta có thể viết lại function trên và return cùng this
với giá trị undefined
:
function makeUser(){ return this; // Lúc này không có object literal } alert( makeUser().name ); // Error: Cannot read property 'name' of undefined
Như bạn có thể thấy kêt quả của alert( makeUser().name )
thì giống như kết quả của alert( user.ref.name )
từ ví dụ trên.
Đây là trường hợp ngược lại:
function makeUser() { return { name: "John", ref() { return this; } }; } let user = makeUser(); alert( user.ref().name ); // John
Bây giờ nó hoạt động, bởi vì user.ref()
là một method. Và giá trị của this
được set là object trước dấu chấm .
.
2. Tạo một máy tính
Tạo một object calculator
với 3 method:
read()
prompt 2 giá trị và lưu chúng như một thuộc tính object.sum()
return tổng của các giá trị được lưu.mul()
nhân các giá trị được lưu và return về kết quả.
let calculator = { // ... your code ... }; calculator.read(); alert( calculator.sum() ); alert( calculator.mul() );
Giải pháp
let calculator = { sum() { return this.a + this.b; }, mul() { return this.a * this.b; }, read() { this.a = +prompt('a?', 0); this.b = +prompt('b?', 0); } }; calculator.read(); alert( calculator.sum() ); alert( calculator.mul() );
3. Chaining – nối chuỗi
Có một ladder
(cái thang) object cho phép đi lên và đi xuống:
let ladder = { step: 0, up() { this.step++; }, down() { this.step--; }, showStep: function() { // shows the current step alert( this.step ); } };
Bây giờ nếu chúng ta cần thực hiện một số lần gọi theo thứ tự dưới đây:
ladder.up(); ladder.up(); ladder.down(); ladder.showStep(); // 1
Sửa đổi code trong up
, down
và showStep
sao cho có thể gọi thành một chuỗi như thế này:
ladder.up().up().down().showStep(); // 1
Cách viết như trên được sử dụng rộng rãi trong các thư viện Javascript
Giải pháp
Giải pháp là return chính object đó mỗi lần gọi
let ladder = { step: 0, up() { this.step++; return this; }, down() { this.step--; return this; }, showStep() { alert( this.step ); return this; } }; ladder.up().up().down().up().down().showStep(); // 1
Chúng ta cũng có thể viết từng dòng như thế này cho dễ đọc nếu chuỗi dài.
ladder .up() .up() .down() .up() .down() .showStep(); // 1
Tham khảo
Bài viết được dịch từ https://javascript.info/object-methods
Mình có đoạn code ntn :
handleSubMit(e) {
e.preventDefault()
const that = this
this.form.validateFields((err,values)=> {
if(!err){
this.$confirm({
title:’Bạn có muốn submit?’,
onOk() {
console.log(this) // trả ra undefind
}
})
}
})
}
=> giải thích hộ mình vs ạ. theo mình nghĩ thì ‘this’ ở đây phải là title vs onOk chứ ạ