Giới Thiệu Proxies Objects Trong ES6
Bài đăng này đã không được cập nhật trong 3 năm
Một trong những tính năng mới được giới thiệu trong đặc tả ES6 cách đây không lâu. Đó là Proxy object. Hôm nay mình sẽ giới thiệu về tầm quan trọng của Proxy
trong các ứng dụng thực tế và cũng hiểu được vì sao đó ra đời, nó sẽ giúp ích gì trong thế giới js đầy biến động này . Mình sẽ giới thiệu khái niệm cơ bản và đưa ra những case study cụ thể để các bạn của rõ hơn về Proxy.
Let's go!
Khái niệm
Đây là khái niệm về Proxy. Ở đây mình sẽ copy nguyên văn trong mà đặc tả ES6 mô tả về Proxy
The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc)
Qua đây các bạn sẽ hiểu được là Proxy nó giúp chúng ta định nghĩa các hành vi tùy biến những hoạt động cơ bản của Object. Ví dụ những hoạt động cơ bản của object đó là: truy cập đến thuộc tính , thay đổi thuộc tính, khởi tạo đối tượng... của object. Thực là khó hiểu phải không? Đối với mình Proxy thực chất là Middleware của object mà thôi. Tại sao mình lại nói như vậy, các bạn chịu khó đọc hết bài này các bạn sẽ hiểu rõ hơn nha
Proxies are Middleware for Javascript Objects
Ở khái niệm mình đã giới thiệu đến các bạn Proxy cho định nghĩa các hành vi tùy biến cho những hoạt động cơ bản của object. Mình sẽ lấy ví dụ hoạt động cơ bản của object là get
giá trị của thuộc tính trong object.
const obj = {
val: 10
};
console.log(obj.val)
Hoặc là set
(tạo) mới thuộc tính cho object. set
cũng là hoạt động cơ bản của object
const obj = {
val: 10
};
obj.val2 = 20;
Ở đây mà chưa hề đề cập đến Proxy. Làm sao để tạo một Proxis (middleware) cho object đây.
Cú pháp
var p = new Proxy(target, handler);
- handler — the placeholder object which contains the trap(s)
- traps — the method(s) that provide property access
- target — object which the proxy virtualizes
Ở trên các bạn sẽ thấy ES6 đặc tả là handler
sẽ chứa các traps
. Một câu hỏi đặt ra khi nào traps
sẽ được kích hoạt. Các bạn đọc lại ở phần định nghĩa mình đã nói rất rõ về vấn đề này. Traps sẽ kích hoạt khi truy cập đến thuộc tính , thay đổi thuộc tính, khởi tạo đối tượng... của object. Trap lại một kĩ thuật trong lập trình khá là hay, mọi người có thể đọc thêm ở đây.
Ví dụ để các bạn hiểu rõ hơn:
const handler = {
get: function() {
console.log('A value has been accessed');
}
}
const initialObj = {
id: 1,![](https://images.viblo.asia/4490d0ed-55a4-4e02-bf61-63cee47dd5e6.png)
name: 'Foo Bar'
}
const proxiedObj = new Proxy(initialObj, handler);
console.log(proxiedObj.name);
Nếu không tạo Proxy
khi gọi proxiedObj.name
, kết quả mong đợi là 'Foo Bar'. Ở đây mình tạo Proxy thì kịch bản sẽ khác, hay nói cách khác là hành động khi get
giá trị value có key là name
đã custom logic hoặc nói cách dễ hơn đó là phải qua tầng middleware handler.get
. Vậy khi thực hiện kết quả trên kết quả chúng ta sẽ trông như thế này.
Khi chúng ta gọi hành động này proxiedObj.name
, function get ở handler object đâu tiên sẽ in sẽ ***"A value has been accessed"***. Rồi sẽ truy cập đến thuộc tính name của object initialObj
rồi sẽ in ra giá trị là ***"Foo Bar"***. Khi các bạn nhìn kĩ hình ảnh của trên nó in ra undefined
. Vì sao lại như vậy nhỉ. Thật ra khi function không return về giá trị nào cả nên sẽ trả về undefined
tại vì function get
sẽ nhận 2 giá trị obj, prop. Obj == initialObj và prop == (name hoặc id).
const handler = {
get: function(obj, prop) {
console.log('A value has been accessed');
return obj[prop]; // Return the value stored in the key being accessed
}
}
const initialObj = {
id: 1,
name: 'Foo Bar'
}
const proxiedObj = new Proxy(initialObj, handler);
console.log(proxiedObj.name);
Kết quả sẽ như chúng ta mong đợi
Giờ chúng ta thêm traps
cho object initialObj là hành động set
(thêm thuộc tính tuổi cho initialObj)
const handler = {
get: function(obj, prop) {
console.log('A value has been accessed');
return obj[prop];
},
set: function(obj, prop, value) {
console.log(`${prop} is being set to ${value}`);
}
}
const initialObj = {
id: 1,
name: 'Foo Bar'
}
const proxiedObj = new Proxy(initialObj, handler);
proxiedObj.age = 24
Khi proxiedObj.age = 24 được thực thi truy cập đến thuộc tính age
và gắn giá trị là 24
thì đồng nghĩa với việc function handler.set
sẽ được kích hoạt đầu tiên và in ra
Qua 2 ví dụ chúng ta đã hiểu mình phần nào đó về Proxy
phải không nào, đơn giả chỉ cần khái báo những traps
để thực hiện theo đúng ý đồ mà chúng ta mong muốn. Đây là list danh sách traps
mà chúng ta cần phải nắm rõ để định nghĩa các trap
theo đúng nhiệm vụ của nó
Ứng dụng thực tế
Ví dụ 1: Thiết lập private cho object
Ta có một object định nghĩa object person:
const person = {
id: 1,
name: 'Foo Bar'
}
Ta muốn thuộc tính id
của person là private và không thể truy cập id của peson. Ta còn tạo một object Proxy có trap
là get
và để kiểm tra khi truy cập đến id
thì báo lỗi
const handler = {
get: function(obj, prop) {
if (prop === 'id') { // Check if the id is being accessed
throw new Error('Cannot access private properties!'); // Throw an error
} else {
return obj[prop]; // If it's not the id property, return it as usual
}
}
}
const person = {
id: 1,
name: 'Foo Bar'
}
const proxiedPerson = new Proxy(person, handler);
console.log(proxiedPerson.id);
Ví dụ 2: Validation
Khi ứng dụng validation cho object thì chúng ta dùng trap
là set
. Chúng ta có thể thêm phần validation tùy ý để hợp với mục đích dữ diệu của chúng ta được ràng buộc
const handler = {
set: function(obj, prop, value) {
if (typeof value !== 'string') {
throw new Error('Only string values can be stored in this object!');
} else {
obj[prop] = value;
}
}
}
const obj = {};
const proxiedObj = new Proxy(obj, handler);
console.log(proxiedObj); // This will log an empty object
proxiedObj.name = 'Foo Bar'; // This should be allowed
console.log(proxiedObj); // This will log an object with the name property set
proxiedObj.age = 24; // This will throw an error.
Ví dụ 3: Extending constructor
Function proxy có thể dễ dàng mở rộng các dependences khi new
một object
function extend(sup, base) {
var descriptor = Object.getOwnPropertyDescriptor(
base.prototype, 'constructor'
);
base.prototype = Object.create(sup.prototype);
var handler = {
construct: function(target, args) {
var obj = Object.create(base.prototype);
this.apply(target, obj, args);
return obj;
},
apply: function(target, that, args) {
sup.apply(that, args);
base.apply(that, args);
}
};
var proxy = new Proxy(base, handler);
descriptor.value = proxy;
Object.defineProperty(base.prototype, 'constructor', descriptor);
return proxy;
}
var Person = function(name) {
this.name = name;
};
var Boy = extend(Person, function(name, age) {
this.age = age;
});
Boy.prototype.sex = 'M';
var Peter = new Boy('Peter', 13);
console.log(Peter.sex); // "M"
console.log(Peter.name); // "Peter"
console.log(Peter.age); // 13
Ví dụ 4: Thao tác với DOM
Đôi khi chúng muốn toggle
ClassName = "aria-selected" của 2 phần tử DOM
let view = new Proxy({
selected: null
},
{
set: function(obj, prop, newval) {
let oldval = obj[prop];
if (prop === 'selected') {
if (oldval) {
oldval.setAttribute('aria-selected', 'false');
}
if (newval) {
newval.setAttribute('aria-selected', 'true');
}
}
// The default behavior to store the value
obj[prop] = newval;
// Indicate success
return true;
}
});
let i1 = view.selected = document.getElementById('item-1');
console.log(i1.getAttribute('aria-selected')); // 'true'
let i2 = view.selected = document.getElementById('item-2');
console.log(i1.getAttribute('aria-selected')); // 'false'
console.log(i2.getAttribute('aria-selected')); // 'true'
Tổng kết
Thông qua Proxy mà ES6 giới thiệu. Chúng ta đã thấy sức mạnh của Proxy qua những ứng dụng tế. Nó giúp chúng ta thay đổi cách tương tác thông thường với object của javascript. Trên đây chỉ là lý thuyết và những ứng dụng rất cơ bản. Còn việc áp dụng nó vào thực tế không hề dễ dàng như chúng ta tưởng tượng sẽ gặp rất nhiều khó khăn. Happy new year Happy coding!
All rights reserved