Sự khác biệt giữa từ khóa riêng và trường riêng trong TypeScript là gì?


27

Trong TypeScript 3.8+, sự khác biệt giữa việc sử dụng privatetừ khóa để đánh dấu một thành viên riêng tư là gì:

class PrivateKeywordClass {
    private value = 1;
}

Và sử dụng các #trường riêng được đề xuất cho JavaScript :

class PrivateFieldClass {
    #value = 1;
}

Tôi có nên thích cái này hơn cái kia không?


Câu trả lời:


43

Từ khóa riêng

Các từ khóa riêng trong nguyên cảo là một thời gian biên dịch chú thích. Nó báo cho trình biên dịch rằng một thuộc tính chỉ có thể truy cập được trong lớp đó:

class PrivateKeywordClass {
    private value = 1;
}

const obj = new PrivateKeywordClass();
obj.value // compiler error: Property 'value' is private and only accessible within class 'PrivateKeywordClass'.

Tuy nhiên, việc kiểm tra thời gian biên dịch có thể dễ dàng được bỏ qua, ví dụ bằng cách bỏ đi thông tin loại:

const obj = new PrivateKeywordClass();
(obj as any).value // no compile error

Các privatetừ khóa cũng không được thực thi trong thời gian chạy

JavaScript phát ra

Khi biên dịch TypeScript thành JavaScript, privatetừ khóa chỉ bị xóa:

class PrivateKeywordClass {
    private value = 1;
}

Trở thành:

class PrivateKeywordClass {
    constructor() {
        this.value = 1;
    }
}

Từ điều này, bạn có thể thấy tại sao privatetừ khóa không cung cấp bất kỳ sự bảo vệ thời gian chạy nào: trong JavaScript được tạo, nó chỉ là một thuộc tính JavaScript bình thường.

Lĩnh vực tư nhân

Các trường riêng đảm bảo rằng các thuộc tính được giữ riêng tư trong thời gian chạy :

class PrivateFieldClass {
    #value = 1;

    getValue() { return this.#value; }
}

const obj = new PrivateFieldClass();

// You can't access '#value' outside of class like this
obj.value === undefined // This is not the field you are looking for.
obj.getValue() === 1 // But the class itself can access the private field!

// Meanwhile, using a private field outside a class is a runtime syntax error:
obj.#value

// While trying to access the private fields of another class is 
// a runtime type error:
class Other {
    #value;

    getValue(obj) {
        return obj.#value // TypeError: Read of private field #value from an object which did not contain the field
    }
}

new Other().getValue(new PrivateKeywordClass());

TypeScript cũng sẽ xuất ra lỗi thời gian biên dịch nếu bạn thử sử dụng trường riêng bên ngoài lớp:

Lỗi khi truy cập vào một lĩnh vực riêng tư

Các trường riêng đến từ một đề xuất JavaScript và cũng hoạt động trong JavaScript bình thường.

JavaScript phát ra

Nếu bạn sử dụng các lĩnh vực tư nhân trong nguyên cảo và đang nhắm mục tiêu phiên bản cũ của JavaScript cho đầu ra của bạn, chẳng hạn như es6hay es2018, nguyên cảo sẽ cố gắng để tạo ra mã mà giả lập hành vi thời gian chạy của lĩnh vực tư nhân

class PrivateFieldClass {
    constructor() {
        _x.set(this, 1);
    }
}
_x = new WeakMap();

Nếu bạn đang nhắm mục tiêu esnext, TypeScript sẽ phát ra trường riêng:

class PrivateFieldClass {
    constructor() {
        this.#x = 1;
    }
    #x;
}

Tôi nên sử dụng cái nào?

Nó phụ thuộc vào những gì bạn đang cố gắng để đạt được.

Các privatetừ khóa là một mặc định tốt. Nó hoàn thành những gì nó được thiết kế để thực hiện và đã được các nhà phát triển TypeScript sử dụng thành công trong nhiều năm. Và nếu bạn đã có một cơ sở mã hiện tại, bạn không cần phải chuyển tất cả mã của mình sang sử dụng các trường riêng. Điều này đặc biệt đúng nếu bạn không nhắm mục tiêu esnext, vì JS mà TS phát ra cho các trường riêng có thể có tác động hiệu suất. Ngoài ra, hãy nhớ rằng các lĩnh vực riêng tư có sự khác biệt tinh tế nhưng quan trọng khác từ từ privatekhóa

Tuy nhiên, nếu bạn cần thực thi quyền riêng tư trong thời gian chạy hoặc xuất ra esnextJavaScript, thì bạn nên sử dụng các trường riêng.

Ngoài ra, hãy nhớ rằng các quy ước của tổ chức / cộng đồng về việc sử dụng cái này hay cái kia cũng sẽ phát triển khi các trường riêng trở nên phổ biến hơn trong các hệ sinh thái JavaScript / TypeScript

Sự khác biệt khác của ghi chú

  • Các trường riêng không được trả về bởi Object.getOwnPropertyNamesvà các phương thức tương tự

  • Các lĩnh vực riêng tư không được tuần tự hóa bởi JSON.stringify

  • Có trường hợp cạnh quan trọng xung quanh thừa kế.

    Ví dụ, TypeScript cấm khai báo một thuộc tính riêng trong một lớp con có cùng tên với một thuộc tính riêng trong lớp cha.

    class Base {
        private value = 1;
    }
    
    class Sub extends Base {
        private value = 2; // Compile error:
    }

    Điều này không đúng với các lĩnh vực riêng tư:

    class Base {
        #value = 1;
    }
    
    class Sub extends Base {
        #value = 2; // Not an error
    }
  • Một thuộc privatetính riêng từ khóa không có trình khởi tạo sẽ không tạo ra khai báo thuộc tính trong JavaScript được phát ra:

    class PrivateKeywordClass {
        private value?: string;
        getValue() { return this.value; }
    }

    Biên dịch thành:

    class PrivateKeywordClass {
        getValue() { return this.value; }
    }

    Trong khi các lĩnh vực tư nhân luôn tạo ra một tuyên bố tài sản:

    class PrivateKeywordClass {
        #value?: string;
        getValue() { return this.#value; }
    }

    Biên dịch thành (khi nhắm mục tiêu esnext):

    class PrivateKeywordClass {
        #value;
        getValue() { return this.#value; }
    }

Đọc thêm:


4

Trường hợp sử dụng: # -private trường

Lời nói đầu:

Biên dịch thời gian gian riêng tư chạy

#lĩnh vực -private cung cấp thời gian biên dịch thời gian chạy riêng tư, mà không phải là "hack". Đó là một cơ chế để ngăn chặn truy cập vào một thành viên từ bên ngoài cơ thể lớp theo bất kỳ cách trực tiếp nào .

class A {
    #a: number;
    constructor(a: number) {
        this.#a = a;
    }
}

let foo: A = new A(42);
foo.#a; // error, not allowed outside class bodies
(foo as any).#bar; // still nope.

Kế thừa lớp an toàn

#lĩnh vực -private có được một phạm vi duy nhất. Hệ thống phân cấp lớp có thể được thực hiện mà không cần ghi đè lên các thuộc tính riêng có tên bằng nhau.

class A { 
    #a = "a";
    fnA() { return this.#a; }
}

class B extends A {
    #a = "b"; 
    fnB() { return this.#a; }
}

const b = new B();
b.fnA(); // returns "a" ; unique property #a in A is still retained
b.fnB(); // returns "b"

Trình biên dịch TS may mắn phát ra lỗi, khi privatecác thuộc tính có nguy cơ bị ghi đè (xem ví dụ này ). Nhưng do bản chất của một tính năng thời gian biên dịch, mọi thứ vẫn có thể xảy ra vào thời gian chạy, các lỗi biên dịch đã cho được bỏ qua và / hoặc mã JS phát ra được sử dụng.

Thư viện bên ngoài

Thư viện tác giả có thể tái cấu trúc # định danh -private mà không gây ra thay đổi đột phá cho khách hàng. Người dùng thư viện ở phía bên kia được bảo vệ khỏi truy cập vào các trường nội bộ.

Bỏ qua API JS # các trường -private

Các hàm và phương thức JS tích hợp bỏ qua #các trường -private. Điều này có thể dẫn đến một lựa chọn tài sản dễ dự đoán hơn vào thời gian chạy. Ví dụ: Object.keys, Object.entries, JSON.stringify, for..invòng lặp và những người khác ( mẫu mã ; cũng thấy Matt Bierner của câu trả lời ):

class Foo {
    #bar = 42;
    baz = "huhu";
}

Object.keys(new Foo()); // [ "baz" ]

Các trường hợp sử dụng: privatetừ khóa

Lời nói đầu:

Truy cập vào API lớp nội bộ và trạng thái (chỉ bảo mật thời gian biên dịch)

privatecác thành viên của một lớp là các thuộc tính thông thường tại thời gian chạy. Chúng ta có thể sử dụng tính linh hoạt này để truy cập API nội bộ của lớp hoặc trạng thái từ bên ngoài. Để đáp ứng kiểm tra trình biên dịch, các cơ chế như xác nhận kiểu, truy cập thuộc tính động hoặc @ts-ignorecó thể được sử dụng giữa các trình khác.

Ví dụ với xác nhận kiểu ( as/ <>) và anygán biến được gõ:

class A { 
    constructor(private a: number) { }
}

const a = new A(10);
a.a; // TS compile error
(a as any).a; // works
const casted: any = a; casted.a // works

TS thậm chí còn cho phép truy cập thuộc tính động của một privatethành viên với một lối thoát :

class C {
  private foo = 10;
}

const res = new C()["foo"]; // 10, res has type number

Truy cập cá nhân có thể có ý nghĩa ở đâu? (1) kiểm tra đơn vị, (2) gỡ lỗi / ghi nhật ký hoặc (3) các tình huống nâng cao khác với các lớp bên trong dự án (danh sách kết thúc mở).

Truy cập vào các biến nội bộ là một chút mâu thuẫn - nếu không, bạn sẽ không thực hiện chúng privateở vị trí đầu tiên. Để đưa ra một ví dụ, các bài kiểm tra đơn vị được cho là các hộp đen / xám với các trường riêng được ẩn làm chi tiết triển khai. Trong thực tế, có thể có các cách tiếp cận hợp lệ từ trường hợp này sang trường hợp khác.

Có sẵn trong mọi môi trường ES

Công cụ privatesửa đổi TS có thể được sử dụng với tất cả các mục tiêu ES. #lĩnh vực -private chỉ có sẵn cho target ES2015/ ES6hoặc cao hơn. Trong ES6 +, WeakMapđược sử dụng nội bộ như triển khai downlevel (xem tại đây ). Các #lĩnh vực bí mật bản địa hiện đang yêu cầutarget esnext .

Tính nhất quán và tương thích

Các nhóm có thể sử dụng các nguyên tắc mã hóa và quy tắc kẻ nói dối để thực thi việc sử dụng privatenhư là công cụ sửa đổi truy cập duy nhất. Hạn chế này có thể giúp thống nhất và tránh nhầm lẫn với #ký hiệu trường -private theo cách tương thích ngược.

Nếu được yêu cầu, các thuộc tính tham số (tốc ký gán của hàm tạo) là một nút chặn hiển thị. Chúng chỉ có thể được sử dụng với privatetừ khóa và chưa có kế hoạch triển khai chúng cho #các lĩnh vực -private.

Những lý do khác

  • privatecó thể cung cấp hiệu suất thời gian chạy tốt hơn trong một số trường hợp xuống cấp (xem tại đây ).
  • Cho đến nay không có phương pháp lớp học riêng cứng nào có sẵn trong TS.
  • Một số người thích privateký hiệu từ khóa tốt hơn 😊.

Lưu ý cả hai

Cả hai cách tiếp cận đều tạo ra một số loại danh nghĩa hoặc nhãn hiệu tại thời điểm biên dịch.

class A1 { private a = 0; }
class A2 { private a = 42; }

const a: A1 = new A2(); 
// error: "separate declarations of a private property 'a'"
// same with hard private fields

Ngoài ra, cả hai đều cho phép truy cập thể hiện chéo: một thể hiện của lớp Acó thể truy cập các thành viên riêng của các Athể hiện khác :

class A {
    private a = 0;
    method(arg: A) {
        console.log(arg.a); // works
    }
}

Nguồn

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.