Chào các bạn, không biết các bạn có biết không chứ kỹ năng chính của mình là PHP thôi. Nhưng dạo gần đây mình bắt đầu tìm hiểu sâu về Javascript để chuẩn bị cho dự án sắp tới của công ty mình đang làm việc. Cách tìm hiểu của mình là ánh xạ những gì mình học được với PHP sang Javascript.
Trong PHP có một khái niệm là magic methods, mình thử tìm hiểu xem trong Javascript có khái niệm nào tương tự như thế này không, nhưng rất tiếc là không. Trong khi đó magic methods là một trong những tính năng mình rất thích ở PHP, sang Javascript không có mình sợ mình không chịu nổi. Thế là mình mới mày mò tìm hiểu cách cài đặt magic methods ở Javascript. Rất may là sau ít giờ tìm hiểu, mình đã tìm ra, các bạn cùng tham khảo nhé.
Mục lục
Hiểu nhanh PHP magic methods
PHP magic methods là những hàm đặc biệt sử dụng trong lập trình hướng đối tượng PHP. Mỗi một magic method sẽ tự động được gọi khi đối tượng của nó xảy ra một sự kiện tương ứng.
method | Được gọi khi |
__construct() | Khi khởi tạo đối tượng |
__destruct() | Khi hủy đối tượng |
__get() | Khi lấy giá trị một thuộc tính |
__set() | Khi gán giá trị cho một thuộc tính |
__call() | Khi gọi một method không tồn tại |
__callStatic() | Khi gọi một method static không tồn tại |
__isset() | Khi gọi hàm isset() hoặc empty() trên một thuộc tính không được phép truy cập. |
Bảng trên mình chỉ liệt kê ra một số magic methods hay sử dụng, còn biết chi tiết hơn thì bạn tham khảo link sau nhé
>> Đọc thêm: PHP magic methods

Javascript không có magic methods nhưng có Proxy
Javascript tuy không hỗ trợ trực tiếp các magic methods trong class như PHP, nhưng lại hỗ trợ gián tiếp thông qua Proxy.
Proxy là một khái niệm trong Javascript được giới thiệu trong phiên bản ES6, cho phép chúng ta có thể can thiệp và làm thay đổi hành vi của một đối tượng như: truy xuất, thiết lập giá trị, thay đổi prototype,… Hiểu rõ hơn các bạn hãy xem qua ví dụ sau
Trong ví dụ này, mình tạo một class là Car
, có một thuộc tính là name
. Sau đó mình khởi tạo một đối tượng myCar
và truy xuất đến thuộc tính name
để log ra console, nhưng mình muốn thuộc tính name
này phải viết in hoa khi log.
class Car {
constructor (name) {
// Gán giá trị cho thuộc tính name
this.name = name
// Điều kỳ diệu nằm ở đoạn này
return new Proxy(this, {
get (target, property) {
// Nếu thuộc tính là `name` thì trả về name được viết in hoa
// Một ví dụ cho việc làm thay đổi hành vi truy xuất dữ liệu của Proxy
if (property == 'name') {
return target[property].toUpperCase()
}
return target[property]
}
})
}
}
let myCar = new Car('Honda civic 2019')
console.log(myCar.name) // HONDA CIVIC 2019
Trong ví dụ trên, mình gán giá trị cho thuộc tính name là Honda civic 2019, tuy nhiên khi truy xuất để in ra console thì lại có giá trị là HONDA CIVIC 2019 (đã được viết in hoa). Tại sao lại kỳ diệu đến như vậy, là do Proxy trong Javascript làm đó.
Hiểu nhanh về Proxy trong Javascript
Trước tiên là một số thuật ngữ:
- target: Đối tượng bạn muốn làm thay đổi hành vi
- traps: Những phương thức để làm thay đổi hành vi của đối tượng
- handler: Một object chứa các traps.
Một số traps
Tên traps | Được gọi khi |
get() | Khi truy xuất một thuộc tính |
set() | Khi gán giá trị cho thuộc tính |
has() | Khi sử dụng toán tử in với object |
Mình chỉ gợi ý một số traps, muốn xem hết thì các bạn xem ở đây nhé
Để khởi tạo một Proxy chúng ta sử dụng cú pháp như sau
const variable = new Proxy(target, handler)
Sử dụng traps handler.get(target, property, receiver)
Traps get() nhận 3 tham số, tuy nhiên bạn chỉ cần quan tâm tới 2 tham số đầu tiên là được.
- target: Là đối tượng cần thay đổi hành vi
- property: thêm của thuộc tính sẽ được thay đổi hành vi
- receiver: đối tượng sau khi đã được gán proxy
Xét thêm một ví dụ nữa, mình có một class User
với hai thuộc tính là username
và password
. Sau đó mình sẽ in ra màn hình hai thông tin này, tuy nhiên thông tin về password
thì mình chỉ hiển thị 3 ký tự đầu tiên, còn các ký tự tiếp theo sẽ bị ẩn thành ký tự *.
let person = {
username: 'admin',
password: 'anhyeuem',
}
var proxyPerson = new Proxy(person, {
get (target, property) {
if (property == 'password') {
let hiddenPassword = ''
for (let i = 0; i < target.password.length; i++) {
if (i < 3) {
hiddenPassword += target.password[i]
} else {
hiddenPassword += '*'
}
}
return hiddenPassword
}
return target[property]
}
})
console.log(proxyPerson.username) // admin
console.log(proxyPerson.password) // anh*****
Mình lấy ví dụ cho cách sử dụng traps get thôi nhé, còn các traps khác thì cũng tương tự thôi.
Cài đặt magic methods như PHP với Js sử dụng Proxy
Mình có một class User
, với 3 thuộc tính là firstname
, lastname
, email
. Mình khởi tạo một đối tượng admin
là một instance của class User
.
Yêu cầu:
- Khi mình viết
admin.fullname
thì sẽ hiển thị ra Fullname của ông admin bằng cách nốilastname
vớifirstname
. - Khi mình gán giá trị cho
email
thì phải kiểm tra xem email có hợp định dạng không, nếu không hợp định dạng thì throw Exception. - Mô phỏng giống PHP magic methods nhất có thể
Phân tích:
Bài toán trên mình sẽ dùng đến Proxy. Khi thực hiện yêu cầu 1, mình sẽ tạo một traps get
, kiểm tra xem nếu property là fullname
thì return một string nối lastname
và firstname
. Khi thực hiện yêu cầu 2, mình sẽ tạo một traps set
, kiểm tra nếu property là email
, thì giá trị của nó phải xuất hiện ít nhất một ký tự @. Để mô phỏng cho giống PHP nhất, thì mình sẽ tách traps get thành method __get()
, và traps set thành method __set()
.
Code full
class User {
constructor (lastname, firstname, email) {
let proxyUser = new Proxy(this, this.magicMethods())
proxyUser.lastname = lastname
proxyUser.firstname = firstname
proxyUser.email = email
return proxyUser
}
__get(property) {
if (property == 'fullname') {
return `${this.lastname} ${this.firstname}`
}
return this[property]
}
__set(property, value) {
if (property == 'email') {
if (value.includes('@') == false) {
throw 'email không hợp lệ'
}
}
this[property] = value
}
magicMethods () {
return {
get (target, property) {
return target.__get(property)
},
set (target, property, value) {
try {
target.__set(property, value)
return true
} catch (error) {
throw error
}
}
}
}
}
let admin = new User('Phạm', 'Quang Bình', 'phambinh217@gmail.com')
console.log(admin.fullname) // Phạm Quang Bình
console.log(admin.email) // phambinh217@gmail.com
let employer = new User('Phạm', 'Quang Minh', 'minhgmail.com') // Throw lỗi email không hợp lệ
console.log(employer.fullname) // Phạm Quang Minh
console.log(employer.email) // undefined
Kết luận
Bài viết trên tả lại thành quả nghiên cứu của mình sau nửa ngày nghiên cứu, nếu có gì thiếu sót rất mong nhận được gạch đá từ các bạn.