Tận dụng sức mạnh của Proxy trong Javascript

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.

proxy-syntax

Đố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ính name 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ính area 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 widthheight 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ả khi width hoặc height 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:

  1. 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ượng catProxy.
    • 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.
  2. 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ụng apply, truyền đúng ngữ cảnh (this) và các tham số gốc.
  3. 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 trap get.
    • 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.

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

Comments

Let’s make a great impact together

Be a part of BraveBits to unlock your full potential and be proud of the impact you make.