TypeScript có ba access modifier (từ khóa phạm vi truy cập) cho thành viên của lớp: public (mặc định), protected và private. Chúng quy định nơi một thành viên có thể được dùng, và trình biên dịch sẽ báo lỗi mọi truy cập từ sai chỗ.
Dòng cuối cùng là lỗi biên dịch, nhưng hãy xem nó in ra gì khi vẫn chạy: 123-45-6789. Đó là điều quan trọng nhất của trang này, và phần private sẽ giải thích.
Mỗi modifier cho phép gì
| Modifier | Bên trong lớp | Trong lớp con | Từ bên ngoài | Áp dụng lúc runtime |
|---|---|---|---|---|
public (mặc định) | có | có | có | không có gì để áp dụng |
protected | có | có | không | không |
private | có | không | không | không |
#name (JavaScript) | có | không | không | có |
readonly | đọc, và ghi trong hàm khởi tạo | đọc | đọc | không |
Các modifier dùng được cho field, phương thức, getter và setter, hàm khởi tạo và parameter property (constructor(private id: string)). Viết public là không bắt buộc, và nhiều codebase bỏ nó đi.
private là kiểm tra lúc biên dịch
TypeScript xóa private cùng với phần còn lại của kiểu. Lớp sau khi biên dịch có một thuộc tính bình thường, nên mọi thứ không đi qua bộ kiểm tra kiểu đều thấy nó: code JavaScript thuần, JSON.stringify, Object.keys, và ngay cả cú pháp ngoặc vuông của chính TypeScript, vốn được cho phép như một lối thoát có chủ đích.
Điều đó ổn với mục đích của private: báo cho các lập trình viên khác (và trình soạn thảo) biết rằng một thành viên là chi tiết triển khai nội bộ. Nó không phải là ranh giới bảo mật, và field private sẽ xuất hiện trong log và response JSON nếu bạn không tự loại bỏ nó.
Field #private: áp dụng lúc runtime
JavaScript có private field riêng, viết bằng #. Engine áp dụng chúng: bên ngoài lớp, obj.#field thậm chí không phải cú pháp hợp lệ, và field này không xuất hiện trong Object.keys, JSON.stringify hay console.log.
Viết s.#token bên ngoài lớp gây lỗi TS18013 (Property '#token' is not accessible outside class 'Session' because it has a private identifier), và khác với trường hợp private, lúc runtime cũng không có cách nào lách được. Xem private field trong JavaScript để biết các quy tắc lúc runtime.
private và #private: nên dùng cái nào
private x | #x | |
|---|---|---|
| Được kiểm tra bởi | trình biên dịch | engine JavaScript |
Thấy được qua Object.keys / JSON.stringify | có | không |
Truy cập bằng ngoặc vuông obj["x"] | được phép | không thể |
Lớp con khai báo x của riêng nó | không, bị xung đột | có, mỗi lớp có #x riêng |
Được sao chép bởi spread { ...obj } | có | không |
| Cú pháp cho phương thức | private helper() | #helper() |
Dùng #private khi dữ liệu phải giữ riêng tư lúc runtime (token, trạng thái nội bộ mà người dùng thư viện không được đụng vào) hoặc khi bạn muốn JSON.stringify bỏ qua nó. Dùng private khi mục tiêu chỉ là một API công khai gọn gàng, khi framework cần đọc field bằng reflection, hoặc để khớp với phong cách sẵn có của codebase. Đừng kết hợp hai cách: private #x gây lỗi TS18010 (An accessibility modifier cannot be used with a private identifier).
protected và lớp con
Thành viên protected dùng được bên trong lớp và trong mọi lớp kế thừa nó, nhưng không dùng được trên instance từ bên ngoài.
Có một quy tắc khiến nhiều người bất ngờ. Bên trong Polygon, bạn có thể đọc sides trên this hoặc trên một Polygon khác, nhưng không được trên một Shape thông thường truyền vào làm tham số: other.sides với other: Shape gây lỗi TS2446 (Property 'sides' is protected and only accessible through an instance of class 'Polygon'. This is an instance of class 'Shape'.). Lớp con chỉ được truy cập thành viên protected của những đối tượng thuộc nhánh của chính nó trong hệ thống lớp.
Lớp con có thể biến một thành viên protected thành public bằng cách khai báo lại, nhưng không thể biến thành viên public thành protected hay private. Khai báo lại kèm giá trị khởi tạo (public override sides = 6) hoặc chỉ khai báo kiểu (declare public sides: number). Một dòng public sides: number; trống trơn sẽ bị từ chối với TS2564 và TS2612, vì với class field hiện đại, nó sẽ đặt lại giá trị kế thừa thành undefined sau khi super() trả về.
readonly
readonly chặn việc gán lại sau khi khởi tạo. Field có thể được gán khi khai báo hoặc trong hàm khởi tạo; mọi lần gán sau đó đều gây lỗi TS2540.
Ở đây có hai giới hạn. readonly chỉ có tác dụng nông: mảng không thể bị thay thế, nhưng nội dung của nó vẫn thay đổi được (đặt kiểu readonly string[] để chặn push). Và giống private, nó biến mất lúc runtime, nên phép gán mà trình biên dịch đã từ chối vẫn được chạy. Với giá trị không được phép thay đổi lúc runtime, hãy dùng Object.freeze hoặc một getter không có setter. Trang về readonly nói về Readonly<T> và mảng readonly.
readonly kết hợp được với các access modifier: private readonly cache = new Map<string, number>() là mẫu phổ biến cho một field nội bộ và không bao giờ bị gán lại.
Lỗi thường gặp
- Coi
privatelà bảo mật. Nó biến mất lúc runtime. Dùng#fieldcho dữ liệu cần được ẩn, và đừng bao giờ đưa thẳng một đối tượng chứa thông tin bí mật vàoJSON.stringify. - Viết
publicở mọi nơi. Đó là mặc định; thêm vào cũng không thay đổi gì. - Dùng
protectedcho mọi thứ "nội bộ". Nếu không có lớp con nào cần,privategiữ phạm vi công khai nhỏ hơn. - Nghĩ rằng
readonlyđóng băng cả dữ liệu lồng nhau. Nó chỉ chặn việc gán lại chính thuộc tính đó.
Câu hỏi thường gặp
TypeScript có những access modifier nào?
public (mặc định: truy cập được ở mọi nơi), protected (bên trong lớp và các lớp con) và private (chỉ bên trong lớp). readonly là một modifier riêng, chặn việc gán lại sau hàm khởi tạo và dùng kết hợp được với cả ba loại trên.
private và #private trong TypeScript khác nhau thế nào?
private chỉ được trình biên dịch kiểm tra; JavaScript sinh ra có một thuộc tính bình thường, nên obj["secret"], JSON.stringify và Object.keys vẫn thấy nó. #secret là private field của JavaScript: runtime áp dụng nó, và code bên ngoài lớp hoàn toàn không đọc được.
private trong TypeScript có thực sự private không?
Chỉ lúc biên dịch. Sau khi biên dịch, field đó là một thuộc tính bình thường mà code JavaScript nào cũng đọc được. TypeScript thậm chí còn cho phép truy cập thành viên private bằng dấu ngoặc vuông (obj["field"]) như một lối thoát. Dùng #field khi tính riêng tư phải được giữ cả lúc runtime.
protected và private trong TypeScript khác nhau thế nào?
Thành viên private chỉ thấy được bên trong lớp khai báo nó. Thành viên protected còn thấy được bên trong các lớp con. Cả hai đều không truy cập được trên một instance từ bên ngoài hệ thống lớp.
Thuộc tính readonly trong TypeScript có thay đổi được không?
Thuộc tính readonly có thể được gán khi khai báo hoặc trong hàm khởi tạo, và không ở đâu khác (nếu không sẽ gây lỗi TS2540). Nó chỉ có tác dụng nông: readonly tags: string[] không thể bị thay thế, nhưng tags.push() vẫn chạy. Dùng readonly string[] để chặn cả việc đó.