Trong hướng dẫn này, bạn sẽ tìm hiểu đối tượng proxy là gì, cùng với những hạn chế của nó.
Chúng tôi cũng sẽ xem xét một số trường hợp sử dụng minh họa cách bạn có thể sử dụng các đối tượng proxy để giải quyết các vấn đề khác nhau.
Không chần chừ nữa, chúng ta hãy bắt đầu.
Mục lục
điều kiện tiên quyết
Tôi thực sự khuyên bạn nên xem qua các chủ đề sau để làm theo cùng với hướng dẫn này:
Proxy là gì?
Từ merriam-webster:
Người được ủy quyền có thể đề cập đến một người được ủy quyền hành động thay cho người khác hoặc có thể chỉ định chức năng hoặc quyền hạn phục vụ thay cho người khác.
Vì vậy, người đại diện không là gì khác ngoài người hòa giải nói hoặc hoạt động thay mặt cho bên nhất định.
Về mặt lập trình, từ proxy còn có nghĩa là một thực thể hoạt động thay mặt cho một đối tượng hoặc một hệ thống. Vì chúng ta đã sắp xếp thuật ngữ này, nên hãy hiểu ý nghĩa của nó trong JavaScript.
Trong JavaScript, có một đối tượng đặc biệt được gọi là Proxy. Nó giúp bạn tạo một đối tượng khác thay cho đối tượng ban đầu.
Đối tượng proxy mới này sẽ đóng vai trò trung gian giữa thế giới thực và đối tượng ban đầu. Bằng cách này, chúng ta sẽ có nhiều quyền kiểm soát hơn đối với sự tương tác với đối tượng ban đầu.
Sử dụng proxy là một cách mạnh mẽ để tương tác với đối tượng hơn là tương tác trực tiếp với nó.
Đây là cú pháp để khai báo một đối tượng proxy:
new Proxy(<object>, <handler>)
Các Proxy
nhận hai tham số:
<object>
: Đối tượng cần proxy.<handler>
: Một đối tượng xác định danh sách các phương thức có thể bị chặn. Đây cũng được gọi là bẫy.
Hãy cùng xem một ví dụ đơn giản:
const books = {
"Deep work": "Cal Newport",
"Atomic Habits": "James Clear"
}
const proxyBooksObj = new Proxy(books, {
get: (target, key) => {
console.log(`Fetching book ${key} by ${target[key]}`);
return target[key];
}
})
Ở đây chúng tôi muốn chặn get
chức năng của đối tượng books
để chúng tôi có thể đăng nhập tên sách và tác giả của cuốn sách vào bảng điều khiển.
Để đạt được điều này, chúng tôi đã tạo một đối tượng proxy mới được gọi là proxyBooksObj
. Đối tượng này được xây dựng bằng cách sử dụng Proxy
chức năng mà chúng ta đã thấy trước đó. phải mất books
là đối tượng được ủy quyền và một đối tượng bao gồm các hàm xử lý cần được giữ lại.
Vì chúng ta cần chặn get
chức năng, chúng tôi đã thêm một get
tài sản chấp nhận một chức năng. Chức năng xử lý này sẽ chấp nhận một chức năng đưa vào target
key
.
Đây là một ví dụ khá đơn giản về proxy trong JavaScript. Có nhiều trường hợp sử dụng proxy khác nhau – ví dụ: nó có thể giúp xác thực, định dạng, thông báo và gỡ lỗi.
Để tìm hiểu thêm về các trình xử lý/bẫy có sẵn, đây là danh sách.
Bây giờ hãy xem một ví dụ sẽ giúp chúng ta hiểu rõ hơn về cách hoạt động của proxy.
Cách hạn chế đối tượng có thuộc tính cụ thể
Đôi khi có thể hữu ích khi hạn chế người dùng để họ chỉ có một thuộc tính cụ thể, giống như những gì useRef
làm trong React. Nó chỉ cho phép chỉnh sửa current
thuộc tính của đối tượng được trả về bởi useRef
.
Hãy tạo một chức năng proxy cho phép người dùng chỉ cập nhật current
thuộc tính và không cho phép chúng tạo bất kỳ thuộc tính nào khác.
const data = {};
const newProxy = new Proxy(data, {
set: function (target, key, value) {
if (key === "current") {
Reflect.set(target, key, value);
return true;
}
return false;
}
});
newProxy.current = 1;
newProxy.point = 1; // Throws error
Ở đây, vì chúng ta đang xử lý việc gán một giá trị mới cho một thuộc tính hiện có hoặc tạo một thuộc tính mới, nên chúng ta sử dụng set
chức năng bẫy. Chúng ta cần khai thác hành vi bên trong của đối tượng.
Các set
chức năng xử lý sẽ đưa vào target
, key
và value
trong đó mục tiêu sẽ là đối tượng đích, khóa và giá trị là thuộc tính và giá trị thực.
Chúng tôi đảm bảo rằng chìa khóa là current
. Khi có, chúng tôi đặt giá trị cho thuộc tính đó và trả về true.
Điều quan trọng là chúng tôi trở lại true
bởi vì chức năng xử lý này dự kiến sẽ tuân theo một số bất biến. Nếu chúng ta không muốn đặt giá trị thì chúng ta nên quay lại false
.
Ngoài ra còn có một số điều cần lưu ý về mỗi chức năng bẫy có sẵn bên trong proxy là bất biến. Bất biến không là gì ngoài một điều kiện cần được tuân theo bởi mọi chức năng bẫy để chức năng cơ bản không thay đổi.
Trích dẫn từ hướng dẫn lập trình Meta của MDN:
Ngữ nghĩa không thay đổi khi thực hiện các hoạt động tùy chỉnh được gọi là bất biến
Một đường vòng nhỏ – Reflect API là gì?
Chúng tôi cũng có thể sử dụng API Reflect tại đây. JavaScript cung cấp một đối tượng tích hợp sẵn có một tập hợp các chức năng có thể giúp chặn các hoạt động của JavaScript. Đây có thể là các hoạt động như set
, get
, apply
và như thế.
Để biết thêm về các phương thức mà Reflect API cung cấp, bạn có thể truy cập liên kết sau.
Với API này, việc thao tác với đối tượng mục tiêu có trong proxy thực sự đơn giản. Một sự thật thú vị về Reflect API là nó không phải là một đối tượng có thể được khởi tạo. Tất cả các phương pháp được cung cấp bởi nó là tĩnh.
Trong ví dụ trước, chúng tôi đã cố gắng trả về giá trị hiện có tại thuộc tính của đối tượng một cách trực tiếp bằng cách truy cập vào đối tượng ban đầu như thế này: target[key]
.
Thay vào đó, ở đây chúng ta có thể sử dụng get
phương pháp Phản ánh. Vì vậy, bây giờ ví dụ trên của chúng tôi sẽ giống như thế này:
const books = {
"Deep work": "Cal Newport",
"Atomic Habits": "James Clear"
}
const proxyBooksObj = new Proxy(books, {
get: (target, key) => {
console.log(`Fetching book ${key} by ${target[key]}`);
return Reflect.get(target, key);
}
})
Bây giờ chúng tôi biết rằng chúng tôi có thể sử dụng Reflect, nhưng một câu hỏi đặt ra – tại sao lại sử dụng Reflect?
Chúng tôi sử dụng Reflect vì nó cho phép chúng tôi triển khai hành vi mặc định của các chức năng đã có trên đối tượng ban đầu. Viết lại theo thuật ngữ kỹ thuật, điều này cho phép chúng tôi triển khai hành vi chuyển tiếp mặc định bên trong các hàm bẫy.
Ngoài ra với Reflect, chúng ta có thể truy cập các chức năng bên trong và áp dụng chúng cho đối tượng được bao bọc bởi proxy.
Vì vậy, việc sử dụng API Reflect bên trong proxy của chúng tôi sẽ có lợi cho chúng tôi.
Cắt mảng như Python
Python là một ngôn ngữ thực sự tuyệt vời. Một tính năng thực sự tuyệt vời mà nó cung cấp là cách bạn có thể cắt các mảng của mình. Ngay cả thư viện NumPy của Python cũng cung cấp tính năng cắt mảng này.
Bạn có thể chỉ cần cung cấp chỉ mục bắt đầu (tùy chọn) và chỉ mục kết thúc (tùy chọn) được phân tách bằng dấu hai chấm. Đây là cú pháp để cắt một mảng trong Python:
arr[<start>:<end>]
Từ cú pháp trên rõ ràng là start
và end
biểu thị start
và end
(độc quyền) vị trí chỉ mục để cắt.
Đây là một ví dụ về cách bạn có thể cắt một mảng trong Python:
data = [1,2,3,4]
print(data[1:3]) # Output: [2, 3]
Đây là cách bạn có thể làm tương tự trong JavaScript:
const data = [1,2,3,4]
console.log(data.slice(1,3)) // Output: [2, 3]
Bạn cần sử dụng slice
chức năng cắt lát data
mảng từ vị trí chỉ mục 1
đến vị trí chỉ mục 3
.
Lưu ý rằng việc cắt một mảng không bao gồm chỉ mục kết thúc được cung cấp trong hàm slice hoặc trong cơ chế cắt của Python.
Sẽ thật tuyệt nếu chúng ta cũng có thể đạt được cơ chế cắt lát của Python trong JavaScript phải không?
Chúng tôi lại có đối tượng Proxy trong JavaScript để giải cứu. Như chúng ta đã thiết lập trong phần trước, proxy là một trình bao bọc xung quanh đối tượng ban đầu. Chúng giúp bạn quản lý tương tác với đối tượng ban đầu.
Hãy tạo lại đối tượng proxy này – nhưng lần này để nó có cơ chế cắt. Dưới đây là mã để triển khai cơ chế cắt trong JavaScript:
const arr = [1,2,3,4];
const arrProxy = new Proxy(arr, {
get: function (target, key) {
if (typeof key === "string" && key.includes(":")) {
let [start, end] = key.split(":");
if (start === "") {
start = 0;
} else if (end === "") {
end = target.length;
} else {
start = parseInt(start, 10);
end = parseInt(end, 10);
}
return Reflect.apply(Array.prototype.slice, target, [start, end]);
}
return Reflect.get(target, key);
}
});
console.log(arrProxy["1:3"]) // [2,3]
Ok, rất nhiều mã ở đây, nhưng hãy lùi lại một bước và hiểu điều gì đang xảy ra:
- Chúng tôi đã tạo một cái mới
proxy
đối tượng trên đầu mảngarr
. Vì chúng tôi đang lập kế hoạch để đạt được việc cắt bằng cách cung cấp vị trí bắt đầu và kết thúc được phân tách bằng dấu hai chấm, nên chúng tôi cần thực hiện một số sửa đổi về cách thực hiện chức năng nhận của một mảng. - Để làm được điều này, chúng ta sẽ sử dụng
get
chức năng bẫy sửa đổi cơ chế lấy cho một mảng. Chúng tôi đảm bảo thêm chức năng bẫy này dưới dạngget
tài sản bên tronghandler
đối tượng củaProxy
. - Hàm get bẫy chấp nhận
target
vàkey
như nó lập luận. Chúng tôi sử dụng điều này để viết logic của chúng tôi. - Vì chúng tôi đang làm một cái gì đó như thế này
arrProxy["1:3"]
trong trường hợp này, khóa sẽ thuộc loại chuỗi và nó sẽ bao gồm:
trong đó. Điều kiện chính của chúng tôi sẽ là phân biệt trên cùng điều kiện này.
if (typeof key === "string" && key.includes(":"))
Tiếp theo, chúng tôi viết một số điều kiện là yêu cầu đối với cơ chế cắt lát trong Python. Dưới đây là các yêu cầu:
- Bất cứ khi nào
start
chỉ mục không được cung cấp, chúng ta nên đặtstart
đến 0. - Bất cứ khi nào
end
chỉ mục không được cung cấp, chúng ta nên đặtend
theo chiều dài của mảng ban đầu. - Nếu cả hai đều được cung cấp, thì chúng tôi chuyển đổi chúng thành số nguyên.
Để làm điều này, chúng ta cần sử dụng hàm slice trong JavaScript.
Chúng tôi sẽ sử dụng phương thức áp dụng của Reflect API để thực thi chức năng slice
với bối cảnh này (target
) là mảng ban đầu và đối số cho slice
chức năng là [start, end]
.
Cuối cùng, nếu key
đối số thuộc bất kỳ loại dữ liệu nào khác ngoài chuỗi, thì chúng tôi trực tiếp trả về mảng với sự trợ giúp của Reflect.get
.
Nhược điểm của việc sử dụng proxy
Bây giờ chúng ta biết cách proxy hoạt động cùng với các trường hợp sử dụng của nó. Nhưng điều quan trọng là phải biết một số nhược điểm của việc sử dụng proxy.
Trước hết, mặc dù sử dụng proxy rất tuyệt, nhưng đó thực sự là một lựa chọn tồi tệ trong các tình huống mà hiệu suất là một yếu tố quan trọng.
Thứ hai, chuyển tiếp proxy không hoạt động là cơ chế chuyển tiếp tất cả các chức năng của mục tiêu bằng cách sử dụng proxy. Đây là cách chuyển tiếp proxy trông như thế nào:
const data = {};
const newData = new Proxy(data, {}); // No trap function.
data.point = { x: 1, y: 2};
console.log(data);
Điều này chỉ hoạt động tốt trên các đối tượng thông thường nhưng sẽ không hoạt động đối với các đối tượng như các nút DOM hoặc các đối tượng có các vị trí bên trong.
Ví dụ: thực hiện thao tác như bên dưới sẽ phát sinh lỗi:
const x = document.createElement("div")
x.className = "hello"
const domProxy = new Proxy(x, {});
console.log(domProxy.getAttribute("class")); // Throws typeError
Điều này xảy ra bởi vì this
giá trị đề cập đến đối tượng proxy thay vì đề cập đến đối tượng ban đầu. Chúng ta có thể giải quyết vấn đề này bằng hàm get bẫy giúp tham chiếu đến đối tượng ban đầu.
const y = new Proxy(x, {
get(target, key) {
const value = Reflect.get(target, key);
if(typeof value === "function"){
return value.bind(target)
}
return value;
}
});
console.log(y.getAttribute("class")); // Output: hello
Tóm lược
Trong bài viết này, chúng ta đã tìm hiểu về proxy trong JavaScript cùng với các trường hợp sử dụng của chúng. Chúng tôi cũng đã xem qua một số nhược điểm của proxy.
Nếu bạn thích ý tưởng sử dụng proxy, thì bạn có thể nâng cấp nó lên và thử sử dụng chúng trong các tình huống xác thực khác nhau hoặc sao chép một số hàm thư viện, chẳng hạn như hàm get và set của lodash.
Đó là tất cả. Cảm ơn vì đã đọc.
theo tôi trên TwitterGitHub và LinkedIn.