Table of Contents
Table of Contents
Giới thiệu về Proxy
Đối tượng Proxy trong JavaScript là một kỹ thuật nâng cao được giới thiệu trong ES6 nhằm cho phép nhà phát triển linh hoạt hơn trong việc can thiệp và tùy chỉnh các phương thức nội bộ trên đối tượng. Sự bổ sung này mở ra nhiều khả năng khác nhau để các nhà phát triển có thể thêm các hành vi rất có ích khi ghi log, xác thực, định dạng và làm sạch dữ liệu đầu vào. Bài viết này nghiên cứu cách triển khai Proxy về mặt khái niệm, cú pháp, cách sử dụng và các ví dụ thực tế.
Proxy cho phép tạo ra một đối tượng mới dựa trên đối tượng gốc với các thuộc tính được định nghĩa từ trước mà các nhà phát triển muốn tích hợp vào các hành vi của đối tượng gốc. Nói cách khác, một đối tượng Proxy có thể được hiểu như một wrapper bọc xung quanh đối tượng gốc. Nó hoạt động như một đối tượng đại diện và đóng vai trò như là cầu nối giữa đối tượng gốc và môi trường bên ngoài.
Cú pháp cơ bản
Để tạo một đối tượng Proxy bao bọc xung quanh một đối tượng gốc, chúng ta khởi tạo một thực thể Proxy mới qua new Proxy()
, nhận đối tượng gốc làm tham số đầu tiên và đối tượng xử lý làm tham số thứ hai.
Đối tượng mục tiêu
Đối tượng mục tiêu là bất kỳ đối tượng JavaScript tiêu chuẩn nào (hoặc mảng, hàm, v.v.) mà bạn muốn bọc bằng Proxy. Đối tượng sẽ tạo tiền đề để Proxy hoạt động.
Đối tượng xử lý
Đối tượng xử lý là nơi phép màu xảy ra. Nó chứa các trap – hay các phương thức dùng để can thiệp các thao tác thực hiện trên đối tượng mục tiêu. Mỗi trap tương ứng với một loại thao tác khác nhau. Ví dụ:
get
: Chặn truy cập thuộc tính.set
: Chặn gán thuộc tính.has
: Chặn toán tửin
.deleteProperty
: Chặn xóa thuộc tính.apply
: Chặn các lệnh gọi hàm.construct
: Chặn toán tửnew
.
Trong ví dụ trên, đối tượng Proxy định nghĩa trap set()
để thêm các hành vi mới vào phương thức nội bộ [[Set]]
của đối tượng obj
. Nếu chúng ta cố gắng truy cập một thuộc tính không tồn tại trên đối tượng obj
, một lỗi thích hợp sẽ được trả về. Nếu không, getter sẽ được thay đổi để trả về giá trị của thuộc tính obj
. Chúng ta có thể thấy rằng, Proxy sẽ trả lỗi nếu chúng ta cố gắng truy cập một thuộc tính không tồn tại trên đối tượng và điều này sẽ không xảy ra nếu không có Proxy.
Ví dụ thực tế
Xác thực dữ liệu
Chúng ta có thể sử dụng Proxy để đánh giá tính hợp lệ của dữ liệu trước khi tạo một thuộc tính mới hoặc cập nhật giá trị của một thuộc tính hiện có cho một đối tượng. Trong ví dụ này:
- Trap
set
được sử dụng để chặn gán thuộc tính. - Trap kiểm tra xem thuộc tính
age
có được gán là một số và thuộc tínhname
không rỗng. - Nếu xác thực không thành công, một lỗi sẽ được trả về; nếu không, thuộc tính sẽ được gán trên đối tượng mục tiêu.
Logging
Proxy có thể làm cho việc log khi truy cập thuộc tính trở nên dễ dàng hơn nhiều khi debug. Ví dụ, bất cứ khi nào chúng ta truy cập một thuộc tính của một đối tượng, chúng ta có thể can thiệp vào phương thức nội bộ [[Get]]
để thêm log.
- Trap
get
sẽ log thông báo bất cứ khi nào một thuộc tính được truy cập. - Trap
set
sẽ log thông báo bất cứ khi nào một thuộc tính được gán một giá trị mới. - Điều này có thể giúp theo dõi cách một đối tượng được tương tác trong môi trường runtime.
Thêm thuộc tính mới
Đối tượng Proxy có thể được sử dụng để tạo các thuộc tính mới tự động tính toán giá trị của chúng dựa trên các thuộc tính khác. Điều này có thể đặc biệt hữu ích cho derived values.
- Trap
get
kiểm tra xem thuộc tínharea
có được truy cập hay không. - Nếu đúng, nó tính toán diện tích dựa trên các thuộc tính
width
vàheight
của đối tượng mục tiêu. - Điều này đảm bảo rằng
area
luôn trả về giá trị chính xác ngay cả khiwidth
hoặcheight
thay đổi.
Thêm các lớp logic
Proxy cũng có thể thêm nhiều logic hơn vào đối tượng, giảm thiểu việc trùng lặp code. Giả sử chúng ta có một đối tượng cat với ba phương thức trong ví dụ sau:
Tại một thời điểm sau đó, nhà phát triển nhận ra rằng cần phải kiểm tra thêm điều kiện xem con mèo có sống không trước khi nó có thể hát, chạy hoặc bắt chuột. Vì lý do này, nhà phát triển dễ có xu hướng thêm điều kiện kiểm tra vào từng phương thức riêng lẻ. Tuy nhiên, giải pháp này lặp đi lặp lại và làm mã nguồn bị trùng lặp. Nếu đối tượng `cat` có 300 phương thức thay vì 3, thì bản Pull Request mới sẽ có 300 dòng thay đổi, điều này là hoàn toàn không thể chấp nhận.
Lúc này nhà phát triển muốn bọc thêm 1 lớp logic xung quanh đối tượng, viết một số phương thức để bảo vệ hay xác thực các thuộc tính của nó trong khi đảm bảo mọi thứ khác vẫn giữ nguyên. May mắn là Proxy tích hợp sẵn trong JavaScript có thể cứu rỗi anh ta vì đây là lúc Proxy phát huy sức mạnh của mình.
Proxy của đối tượng cat này hoạt động theo ba bước:
-
Can thiệp vào các lệnh gọi phương thức:
- Trap
get
chặn truy cập thuộc tính trên đối tượngcatProxy
. - Khi một thuộc tính được truy cập, trap
get
kiểm tra xem nó có phải là một hàm (phương thức) hay không.
- Trap
-
Bọc các phương thức với logic bổ sung:
- Nếu thuộc tính được truy cập là một hàm, trap
get
trả về một hàm mới. - Hàm mới này đầu tiên kiểm tra cờ
isDead
. - Nếu
isDead
là true thì trả lỗi, ngăn không cho phương thức được thực thi. - Nếu
isDead
là false, nó gọi phương thức gốc bằng cách sử dụngapply
, truyền đúng ngữ cảnh (this
) và các tham số gốc.
- Nếu thuộc tính được truy cập là một hàm, trap
-
Tránh lặp lại logic
- Logic bổ sung (kiểm tra cờ
isDead
) được định nghĩa chỉ một lần trong trapget
. - Logic này được áp dụng cho tất cả các phương thức một linh động mà không cần phải sửa đổi từng phương thức riêng lẻ.
- Điều này tránh việc phải thêm cùng một kiểm tra
if (isDead) { throw new Error(...) }
vào mỗi phương thức của đối tượng cat.
- Logic bổ sung (kiểm tra cờ
Bằng cách sử dụng Proxy với trap get
, bạn có thể tập trung các logic bổ sung (như kiểm tra isDead
) vào một chỗ, tránh việc phải sửa đổi hoặc sao chép logic này qua nhiều phương thức. Điều này làm cho mã dễ bảo trì hơn và dễ dàng mở rộng sau này.
Kết luận
Bằng cách hiểu và tận dụng Proxy, các nhà phát triển có thể tạo ra mã nguồn đáng tin cậy và dễ bảo trì hơn. Proxy cho phép tạo ra một đối tượng mới dựa trên đối tượng gốc với các thuộc tính và hành vi, hoạt động như một wrapper của đối tượng gốc. Phương pháp này giúp tập trung logic, giảm sự sao chép mã và đảm bảo hành vi nhất quán trên toàn ứng dụng.
SonVH