Menu

Private field trong JavaScript: cú pháp # để ẩn dữ liệu lớp

Cách tiền tố # biến trường và phương thức của lớp thành private thật sự trong JavaScript: cú pháp, quy tắc và vì sao quy ước dấu gạch dưới là chưa đủ.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Vấn đề của dấu gạch dưới

Trong nhiều năm, JavaScript không có private field. Quy ước khi đó là thêm dấu gạch dưới vào trước tên thuộc tính và mong mọi người tôn trọng nó:

_count trông như private nhưng không phải. Bất kỳ code gọi nào cũng đọc, ghi hoặc xóa được nó. Dấu gạch dưới chỉ là tấm biển lịch sự treo trên cửa, còn cánh cửa thì vẫn mở toang.

JavaScript hiện đại đã khắc phục chuyện này bằng private field thật sự, đánh dấu bằng #.

Dấu # biến trường thành private

Thêm tiền tố # vào tên trường khi khai báo và ở mọi chỗ truy cập:

Bên trong lớp, this.#count hoạt động bình thường. Từ bên ngoài, nó như không tồn tại:

# thực sự là một phần của tên trường. Nó không phải từ khóa bổ nghĩa như private ở các ngôn ngữ khác, mà là ký hiệu để bộ phân tích cú pháp tra cứu một ô riêng, được bảo vệ trên đối tượng. Vì vậy lỗi xuất hiện ngay lúc phân tích cú pháp, trước cả khi code chạy.

Phương thức và getter private

Không chỉ trường mới đánh dấu private được. Phương thức, getter và setter đều nhận tiền tố #:

#assertPositive là hàm trợ giúp nội bộ. Nó không thuộc API công khai, nên khi biến nó thành private thật sự thì không ai vô tình gọi nó từ bên ngoài, và cũng không ai phụ thuộc vào nó được. Nhờ đó bạn vẫn thoải mái đổi tên hoặc xóa nó sau này.

Thành viên static private

Thành viên static cũng có thể là private. Thêm tiền tố # theo cách tương tự:

Static private nằm trên chính lớp, không nằm trên instance. Hữu ích cho bộ đếm, bộ nhớ đệm hoặc cấu hình không nên lọt ra khỏi lớp.

Lớp con không nhìn thấy chúng

Điều này hay làm khó người chuyển từ Java hoặc C# sang. Private field của JavaScript là class-private, không phải instance-private. Lớp con không với tới được private field của lớp cha:

JavaScript không có protected. Nếu lớp con cần dữ liệu, lớp cha phải cung cấp phương thức, getter hoặc (ít gặp hơn) một trường không private. Thiết kế này có chủ ý: private nghĩa là private thật, và kế thừa (inheritance) không tạo lỗ hổng nào trong đó.

Kiểm tra private field bằng in

Đôi khi bạn muốn xác nhận một đối tượng thực sự thuộc về lớp của mình, gọi là brand check. Toán tử in dùng được với tên private field bên trong lớp:

Vì chỉ Wallet mới tạo được đối tượng có #balance, nên #balance in obj là phép kiểm tra đáng tin cậy rằng obj là một instance Wallet thật. Trong một số trường hợp biên, cách này nhanh và an toàn hơn instanceof, vì private field không thể bị giả mạo từ bên ngoài.

Lỗi hay gặp: đối tượng thường không có private field

Private field nằm trên các instance do hàm khởi tạo của lớp tạo ra. Nếu bạn dùng nó trên một đối tượng không được tạo bằng new, code sẽ ném lỗi:

Gọi phương thức với this không phải Point sẽ ném lỗi lúc chạy. Đây chính là cơ chế đứng sau brand check ở trên: private field gắn với đúng lớp đã khai báo nó, không gắn với bất kỳ đối tượng nào chỉ tình cờ trông giống.

Khi nào nên dùng #

Hãy mặc định dùng private field mỗi khi một trạng thái hay hàm trợ giúp không thuộc API công khai của lớp. Lý do:

  • Tự do tái cấu trúc. Code gọi không thể phụ thuộc vào phần bên trong mà chúng hoàn toàn không nhìn thấy.
  • Đóng gói thật sự. Không có chuyện code bên ngoài vô tình đọc, ghi hay xóa.
  • Gợi ý tự động gọn hơn. Trình soạn thảo không gợi ý thành viên private cho code gọi từ bên ngoài.

Dùng thuộc tính công khai khi thứ đó thật sự thuộc giao diện của lớp. Dùng getter (get name()) khi bạn muốn cho đọc private field mà không cho ghi. Bỏ quy ước dấu gạch dưới đi: đó chỉ là cách chữa cháy cho một khoảng trống mà giờ ngôn ngữ đã lấp đầy.

#celsius là nơi lưu trữ ẩn; celsius và fahrenheit là các góc nhìn chỉ đọc. Code gọi không thể làm hỏng trạng thái bên trong, và lớp vẫn tự do thay đổi cách lưu giá trị sau này.

Tiếp theo: prototype

Lớp phần lớn chỉ là cú pháp rút gọn (syntactic sugar) bên trên hệ thống prototype của JavaScript, mô hình lâu đời và nền tảng hơn mà ngôn ngữ thật sự được xây dựng trên đó. Hiểu prototype giúp bạn hiểu vì sao this hoạt động như vậy, kế thừa thực sự vận hành thế nào và một lớp extends điều gì ở bên dưới. Đó là nội dung trang tiếp theo.

Câu hỏi thường gặp

Làm sao khai báo private field trong JavaScript?

Thêm tiền tố # vào tên trường, cả khi khai báo lẫn mỗi lần truy cập. class Counter { #count = 0; increment() { this.#count++; } } tạo ra một trường private thật sự. # là một phần của tên, không phải toán tử.

#field và _field trong JavaScript khác nhau thế nào?

_field chỉ là quy ước đặt tên: thuộc tính đó vẫn công khai và ai cũng đọc hoặc ghi được. #field do chính ngôn ngữ đảm bảo: code bên ngoài lớp hoàn toàn không truy cập được, và nếu cố truy cập sẽ gặp SyntaxError ngay lúc phân tích cú pháp. Dùng # khi bạn cần tính riêng tư thật sự.

Lớp con có truy cập được private field của lớp cha không?

Không. Private field chỉ thuộc về lớp khai báo nó, ngay cả lớp con cũng không chạm vào được. Nếu lớp con cần truy cập, lớp cha phải cung cấp một phương thức hoặc getter. Cách này chặt hơn protected ở các ngôn ngữ khác, và đó là chủ ý.

Có kiểm tra được một đối tượng có private field hay không?

Có, bằng toán tử in bên trong lớp: #field in obj trả về true nếu obj có private field đó. Cách này hữu ích cho brand check, tức là xác nhận một đối tượng đúng là instance của lớp bạn trước khi gọi phương thức trên nó.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu