Trong những năm qua, JavaScript đã phát triển thành một ngôn ngữ lập trình mạnh mẽ và dễ thích nghi, không ngừng phát triển để đáp ứng nhu cầu thay đổi của các nhà phát triển.
Một trong những tiến bộ tương đối gần đây của nó là đối tượng Proxy, cho phép các lập trình viên tạo ra các đối tượng linh hoạt và mạnh mẽ có khả năng ngăn chặn và sửa đổi các thao tác chính trên các đối tượng khác.
Bài viết này đi sâu vào các khả năng của Proxy trong JavaScript , bao gồm cú pháp, thuộc tính, ứng dụng điển hình, điểm mạnh, hạn chế, trường hợp minh họa và phương pháp tiếp cận được đề xuất.
Proxy là một đối tượng bao bọc một đối tượng khác và chặn các hoạt động cơ bản trên đó, chẳng hạn như truy cập, gán và xóa các thuộc tính. Proxy là một khía cạnh quan trọng của JavaScript giúp trao quyền cho các nhà phát triển viết mã linh hoạt và mạnh mẽ hơn.
Mục tiêu của bài viết này là cung cấp sự hiểu biết toàn diện về Proxy trong JavaScript, bao gồm cú pháp, đặc điểm, lợi ích, nhược điểm, minh họa và các kỹ thuật được đề xuất.
Proxy của JavaScript là một khả năng cho phép tạo ra các đối tượng có khả năng sửa đổi và tùy chỉnh các hoạt động cơ bản được thực hiện trên các đối tượng khác.
Để thiết lập một đối tượng Proxy, cần có hai thành phần: đối tượng đích và đối tượng xử lý. Đối tượng mục tiêu là đối tượng mà các hoạt động sẽ bị chặn, trong khi đối tượng xử lý chịu trách nhiệm giữ các bẫy hoặc phương thức được sử dụng để bắt các hoạt động này.
Đây là một ví dụ minh họa cách tạo một đối tượng Proxy cơ bản:
const target = { name: 'John', age: 25, }; const handler = { get: function(target, prop) { console.log(`Getting property ${prop}`); return target[prop]; }, }; const proxy = new Proxy(target, handler); console.log(proxy.name); // Getting property name // John
Trong ví dụ này, chúng tôi tạo một đối tượng mục tiêu có hai đặc điểm: tên và tuổi. Chúng tôi cũng tạo một đối tượng xử lý có bẫy get để nắm bắt mọi nỗ lực đọc thuộc tính trên đối tượng đích. Sau đó, chúng tôi tạo ra một đối tượng Proxy bằng cách cung cấp các đối tượng đích và trình xử lý cho hàm tạo Proxy. Cuối cùng, chúng tôi truy xuất thuộc tính tên của đối tượng Proxy, gọi bẫy get và xuất một thông báo tới bảng điều khiển.
Bẫy là các phương thức chặn các hoạt động trên đối tượng đích. Có một số bẫy mà bạn có thể sử dụng với đối tượng Proxy, bao gồm get, set, has, deleteProperty, v.v.
Dưới đây là tổng quan ngắn gọn về một số bẫy được sử dụng phổ biến nhất:
get : Cái bẫy này chặn các nỗ lực đọc một thuộc tính trên đối tượng đích. Nó nhận hai đối số: đối tượng đích và thuộc tính đang được truy cập. Trap trả về giá trị của thuộc tính.
set : Cái bẫy này nắm bắt bất kỳ nỗ lực nào để thiết lập một thuộc tính trên đối tượng mục tiêu. Nó yêu cầu ba tham số: chính đối tượng đích, thuộc tính đang được thiết lập và giá trị cập nhật của thuộc tính đó. Cơ chế này có khả năng thay đổi giá trị được thiết lập hoặc nó có thể tạo ra lỗi để ngăn giá trị được thiết lập.
has : Cái bẫy này chặn các nỗ lực kiểm tra xem một thuộc tính có tồn tại trên đối tượng đích hay không. Nó nhận hai đối số: đối tượng đích và thuộc tính đang được kiểm tra. Cái bẫy trả về một giá trị boolean cho biết thuộc tính có tồn tại hay không.
deleteProperty : Cái bẫy này chặn các nỗ lực xóa một thuộc tính khỏi đối tượng đích. Nó nhận hai đối số: đối tượng đích và thuộc tính bị xóa. Cái bẫy có thể xóa thuộc tính hoặc đưa ra lỗi để ngăn không cho thuộc tính bị xóa.
Các đối tượng proxy sở hữu một đặc điểm hấp dẫn cho phép chúng bị vô hiệu hóa, dẫn đến các bẫy của chúng không còn chặn các hoạt động trên đối tượng mục tiêu. Để xây dựng một đối tượng Proxy có thể bị vô hiệu hóa, hãy sử dụng hàm Proxy.revocable()
.
Đây là một ví dụ:
const target = { name: 'John', age: 25, }; const handler = { get: function(target, prop) { console.log(`Getting property ${prop}`); return target[prop]; }, }; const {proxy, revoke} = Proxy.revocable(target, handler); console.log(proxy.name); // Getting property name // John revoke(); console.log(proxy.name); // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
Trong ví dụ này, chúng tôi tạo một đối tượng Proxy có thể huỷ ngang bằng cách sử dụng phương thức Proxy.revocable()
. Sau đó, chúng tôi truy cập thuộc tính tên của đối tượng Proxy, đối tượng này sẽ kích hoạt bẫy get và ghi một thông báo vào bảng điều khiển. Sau đó, chúng tôi thu hồi đối tượng Proxy bằng cách sử dụng phương thức revoke()
, điều đó có nghĩa là mọi nỗ lực tiếp theo để truy cập các thuộc tính trên đối tượng Proxy sẽ không thành công.
Một tính năng thú vị khác của các đối tượng Proxy là chúng có thể được sử dụng để triển khai các mẫu kế thừa trong JavaScript. Bằng cách sử dụng một đối tượng Proxy làm nguyên mẫu của một đối tượng khác, bạn có thể chặn tra cứu thuộc tính và tùy chỉnh hành vi của chuỗi nguyên mẫu.
Đây là một ví dụ:
const parent = { name: 'John', }; const handler = { get: function(target, prop) { console.log(`Getting property ${prop}`); if (!(prop in target)) { return Reflect.get(parent, prop); } return target[prop]; }, }; const child = new Proxy({}, handler); console.log(child.name); // Getting property name // John child.name = 'Bob'; console.log(child.name); // Getting property name // Bob console.log(parent.name); // John
Trong ví dụ này, đối tượng cha được xác định và có thuộc tính tên. Sau đó, chúng tôi tạo một đối tượng trình xử lý có bẫy nhận để ngăn mọi yêu cầu đọc đối với các thuộc tính của đối tượng con. Bẫy sử dụng phương thức Reflect.get() để quay trở lại đối tượng cha nếu thuộc tính không có trong đối tượng con.
Sau đó, sử dụng một đối tượng Proxy làm nguyên mẫu và đối tượng xử lý làm trình xử lý, chúng ta xây dựng một đối tượng con. Cuối cùng, chúng tôi có quyền truy cập và thay đổi thuộc tính name của đối tượng con, thuộc tính này đặt bẫy get và set cũng như ghi nhật ký thông báo vào bảng điều khiển.
Một trường hợp sử dụng cho Proxy là lưu trữ các lệnh gọi chức năng đắt tiền. Trong ví dụ này, chúng tôi tạo một đối tượng Proxy lưu trữ kết quả của một lệnh gọi hàm dựa trên các đối số của nó.
function calculateCost(price, taxRate) { console.log('Calculating cost...'); return price * (1 + taxRate); } const cache = new Map(); const proxy = new Proxy(calculateCost, { apply(target, thisArg, args) { const key = args.join('-'); if (cache.has(key)) { console.log('Returning cached result...'); return cache.get(key); } else { const result = Reflect.apply(target, thisArg, args); cache.set(key, result); return result; } }, }); console.log(proxy(10, 0.2)); // Calculating cost... 12 console.log(proxy(10, 0.2)); // Returning cached result... 12 console.log(proxy(20, 0.2)); // Calculating cost... 24 console.log(proxy(20, 0.3)); // Calculating cost... 26 console.log(proxy(20, 0.3)); // Returning cached result... 26
Trong ví dụ này, chúng tôi xác định một hàm có tên là calculateCost
nhận giá và thuế suất rồi trả về chi phí kèm theo thuế. Sau đó, chúng tôi tạo một đối tượng bộ đệm bằng lớp Map
.
Tiếp theo, chúng ta tạo một đối tượng Proxy được gọi là proxy
để chặn các lệnh gọi hàm bằng cách sử dụng bẫy apply
. Bẫy apply
được gọi bất cứ khi nào hàm được gọi và nó nhận các đối số của hàm dưới dạng một mảng. Chúng tôi sử dụng các đối số để tạo khóa bộ đệm và kiểm tra xem kết quả đã có trong bộ đệm chưa. Nếu đúng như vậy, chúng tôi trả về kết quả được lưu trong bộ nhớ cache. Nếu không, chúng tôi tính toán kết quả và lưu trữ nó trong bộ đệm.
Cuối cùng, chúng tôi gọi hàm proxy
bằng cách sử dụng các đối số khác nhau và quan sát thấy rằng kết quả được lưu trữ trong bộ đệm cho các cuộc gọi tiếp theo với các đối số giống hệt nhau.
Một trường hợp sử dụng khác cho Proxy là xác thực các thuộc tính của đối tượng. Trong ví dụ này, chúng tôi tạo một đối tượng Proxy xác thực độ dài của thuộc tính chuỗi.
const user = { name: 'John', password: 'secret', }; const proxy = new Proxy(user, { set(target, prop, value) { if (prop === 'password' && value.length < 8) { throw new Error('Password must be at least 8 characters long'); } target[prop] = value; return true; }, }); console.log(proxy.name); // John console.log(proxy.password); // secret proxy.password = '12345678'; console.log(proxy.password); // 12345678 proxy.password = '123'; // Error
Trong ví dụ này, chúng tôi định nghĩa một đối tượng được gọi là user
với thuộc tính name
và password
. Sau đó, chúng tôi tạo một đối tượng Proxy được gọi là proxy
chặn các phép gán thuộc tính bằng cách sử dụng bẫy set
. set
bẫy được gọi bất cứ khi nào một thuộc tính được gán và nó nhận tên thuộc tính, giá trị mới và đối tượng đích.
Chúng tôi sử dụng bẫy set
để kiểm tra xem thuộc tính được chỉ định có phải là thuộc tính password
và liệu giá trị có dài hơn 8 ký tự hay không. Nếu có, chúng tôi ném một lỗi. Mặt khác, chúng tôi đặt giá trị thuộc tính trên đối tượng đích.
Chúng tôi sử dụng đối tượng proxy
để gán các giá trị khác nhau cho thuộc tính password
và lưu ý rằng bất kỳ giá trị nào có độ dài dưới 8 ký tự đều gây ra lỗi.
Một trường hợp sử dụng phổ biến khác cho Proxy là ghi lại các truy cập và gán thuộc tính đối tượng. Trong ví dụ này, chúng tôi tạo một đối tượng Proxy ghi nhật ký truy cập và gán thuộc tính.
const user = { name: 'John', email: '[email protected]', }; const proxy = new Proxy(user, { get(target, prop) { console.log(`Getting ${prop} property`); return target[prop]; }, set(target, prop, value) { console.log(`Setting ${prop} property to ${value}`); target[prop] = value; return true; }, }); console.log(proxy.name); // Getting name property -> John proxy.email = '[email protected]'; // Setting email property to [email protected] console.log(proxy.email); // Getting email property -> [email protected]
Trong ví dụ này, chúng tôi định nghĩa một đối tượng được gọi là user
có name
và thuộc tính email
. Sau đó, chúng tôi tạo một đối tượng Proxy được gọi là proxy
chặn các truy cập và gán thuộc tính bằng cách sử dụng bẫy get
và set
.
Bẫy get
được gọi bất cứ khi nào một thuộc tính được truy cập và nó nhận tên thuộc tính và đối tượng đích. Trong ví dụ này, chúng tôi ghi một thông báo vào bảng điều khiển cho biết rằng thuộc tính đang được truy cập và sau đó chúng tôi trả về giá trị thuộc tính từ đối tượng đích.
set
bẫy được gọi bất cứ khi nào một thuộc tính được gán và nó nhận tên thuộc tính, giá trị mới và đối tượng đích. Trong ví dụ này, chúng tôi ghi một thông báo vào bảng điều khiển cho biết rằng thuộc tính đang được chỉ định, sau đó chúng tôi đặt giá trị thuộc tính trên đối tượng đích.
Cuối cùng, chúng tôi truy cập và gán các thuộc tính khác nhau bằng cách sử dụng đối tượng proxy
và quan sát thấy các thông báo được ghi vào bảng điều khiển.
Hành vi có thể tùy chỉnh : Với các đối tượng proxy, bạn có thể chặn và tùy chỉnh các thao tác cơ bản trên các đối tượng khác, cho phép bạn tạo các tính năng nâng cao như kiểm soát truy cập, lưu vào bộ nhớ đệm và ghi nhật ký.
Kế thừa : Các đối tượng proxy cung cấp khả năng triển khai các mẫu kế thừa trong JavaScript, điều này có thể dẫn đến mã linh hoạt hơn và có thể mở rộng hơn.
Có thể hủy bỏ : Các đối tượng proxy có thể bị vô hiệu hóa hoặc thu hồi sau khi chúng được tạo, giúp chúng hữu ích cho việc giới hạn phạm vi của đối tượng proxy hoặc vì lý do bảo mật.
Mặc dù thực tế là Proxy đã đồng hành cùng chúng ta trong một thời gian dài, nhưng không phải tất cả các phiên bản trình duyệt đều có thể hỗ trợ chức năng này.
Hơn nữa, việc sử dụng Proxy có thể ảnh hưởng tiêu cực đến hiệu suất ứng dụng của bạn, đặc biệt nếu bạn sử dụng chúng quá thường xuyên.
Điều quan trọng là phải hiểu ý nghĩa của việc sử dụng proxy. Nó không nên được tin cậy cho những thời điểm quan trọng của ứng dụng, chẳng hạn như xác thực quan trọng đầu vào của người dùng.
Lưu ý về các hạn chế : Trước khi triển khai proxy trong mã của bạn, hãy lưu ý các hạn chế mà proxy áp đặt và cách chúng có thể ảnh hưởng đến tốc độ và tính bảo mật của ứng dụng của bạn.
Các đối tượng proxy chỉ nên được sử dụng khi thực sự cần thiết vì chúng có thể ảnh hưởng đến hiệu suất mã của bạn.
Kiểm tra cẩn thận : Khi sử dụng các đối tượng Proxy, hãy đảm bảo kiểm tra cẩn thận và cảnh giác với bất kỳ hành vi bất ngờ tiềm ẩn nào.
Tuân thủ các quy tắc : Để làm cho mã của bạn dễ đọc và dễ bảo trì, hãy tuân thủ các quy ước và phương pháp hay nhất được chấp nhận trong khi triển khai các đối tượng Proxy.
Bài viết đi sâu vào các tính năng nâng cao của Proxy, chẳng hạn như các mẫu kế thừa và khả năng tạo các đối tượng Proxy có thể thu hồi.
Bất kể cấp độ kinh nghiệm của bạn với tư cách là nhà phát triển, việc hiểu Proxy trong JavaScript là điều cơ bản để nâng cấp mã của bạn lên cấp độ cao hơn.
Do khả năng thích ứng và hiệu quả của nó, Proxy tạo thành một công cụ quan trọng cho bất kỳ nhà phát triển JavaScript nào mong muốn xây dựng các ứng dụng phức tạp một cách dễ dàng.