Apa perbedaan antara kata kunci pribadi dan bidang pribadi dalam TypeScript?

Jawaban:

43

Kata kunci pribadi

Kata kunci pribadi dalam TypeScript adalah anotasi waktu kompilasi . Ini memberitahu kompiler bahwa properti hanya dapat diakses di dalam kelas itu:

class PrivateKeywordClass {
    private value = 1;
}

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

Namun kompilasi pengecekan waktu dapat dengan mudah dilewati, misalnya dengan membuang informasi jenis:

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

Itu private kunci juga tidak diberlakukan saat runtime

JavaScript yang Dipancarkan

Saat mengkompilasi TypeScript ke JavaScript, privatekata kunci dihapus:

class PrivateKeywordClass {
    private value = 1;
}

Menjadi:

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

Dari ini, Anda dapat melihat mengapa privatekata kunci tidak menawarkan perlindungan runtime: di JavaScript yang dihasilkan itu hanya properti JavaScript normal.

Bidang pribadi

Bidang pribadi memastikan bahwa properti dirahasiakan saat runtime :

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 juga akan menampilkan kesalahan waktu kompilasi jika Anda mencoba menggunakan bidang pribadi di luar kelas:

Kesalahan saat mengakses bidang pribadi

Bidang pribadi berasal dari proposal JavaScript dan juga berfungsi dalam JavaScript normal.

JavaScript yang Dipancarkan

Jika Anda menggunakan bidang pribadi dalam TypeScript dan menargetkan versi JavaScript yang lebih lama untuk hasil Anda, seperti es6atau es2018, TypeScript akan mencoba membuat kode yang mengemulasi perilaku runtime bidang pribadi

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

Jika Anda menargetkan esnext, TypeScript akan memancarkan bidang pribadi:

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

Yang mana yang harus saya gunakan?

Itu tergantung pada apa yang ingin Anda capai.

Kata privatekunci adalah standar yang baik. Itu mencapai apa yang dirancang untuk dicapai dan telah berhasil digunakan oleh pengembang TypeScript selama bertahun-tahun. Dan jika Anda memiliki basis kode yang ada, Anda tidak perlu mengalihkan semua kode Anda untuk menggunakan bidang pribadi. Ini terutama benar jika Anda tidak menargetkan esnext, karena JS yang dipancarkan TS untuk bidang pribadi mungkin memiliki dampak kinerja. Juga perlu diingat bahwa bidang pribadi memiliki perbedaan halus namun penting lainnya dariprivate kata kunci

Namun jika Anda perlu menegakkan privasi runtime atau mengeluarkan esnext JavaScript, Anda harus menggunakan bidang pribadi.

Perlu diingat juga bahwa konvensi organisasi / komunitas tentang penggunaan satu atau yang lain juga akan berkembang ketika bidang pribadi menjadi lebih luas dalam ekosistem JavaScript / TypeScript

Perbedaan catatan lainnya

  • Bidang pribadi tidak dikembalikan oleh Object.getOwnPropertyNamesdan metode serupa

  • Bidang pribadi tidak diserialisasi oleh JSON.stringify

  • Ada kasus tepi penting di sekitar warisan.

    TypeScript misalnya melarang menyatakan properti pribadi di subkelas dengan nama yang sama dengan properti pribadi di superclass.

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

    Ini tidak benar dengan bidang pribadi:

    class Base {
        #value = 1;
    }
    
    class Sub extends Base {
        #value = 2; // Not an error
    }
  • Sebuah privatemilik pribadi kunci tanpa initializer tidak akan menghasilkan deklarasi properti di dipancarkan JavaScript:

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

    Kompilasi ke:

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

    Sedangkan bidang pribadi selalu menghasilkan deklarasi properti:

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

    Kompilasi ke (saat menargetkan esnext):

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

Bacaan lebih lanjut:

Matt Bierner
sumber
4

Gunakan kasing: # bidang -private

Kata pengantar:

Privasi waktu kompilasi dan run-time

#bidang -private menyediakan privasi waktu kompilasi dan run-time, yang tidak "bisa diretas". Ini adalah mekanisme untuk mencegah akses ke anggota dari luar badan kelas dengan cara langsung apa pun .

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.

Warisan kelas yang aman

#Bidang -private mendapatkan cakupan yang unik. Hirarki kelas dapat diimplementasikan tanpa ditimpa secara tidak sengaja properti pribadi dengan nama yang sama.

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"

Kompiler TS untungnya memancarkan kesalahan, ketika privateproperti dalam bahaya ditimpa (lihat contoh ini ). Tetapi karena sifat fitur kompilasi-waktu semuanya masih mungkin pada saat dijalankan, mengingat kesalahan kompilasi diabaikan dan / atau kode JS yang dipancarkan digunakan.

Perpustakaan eksternal

Penulis perpustakaan dapat melakukan refactor #pengidentifikasi pribadi tanpa menyebabkan perubahan besar bagi klien. Pengguna perpustakaan di sisi lain dilindungi dari mengakses bidang internal.

API JS menghilangkan #bidang -private

Fungsi dan metode JS #bawaan mengabaikan bidang -privat. Ini dapat menghasilkan pemilihan properti yang lebih mudah diprediksi pada saat run-time. Contoh: Object.keys, Object.entries, JSON.stringify, for..inlingkaran dan lain-lain ( kode contoh , lihat juga Matt Bierner ini jawaban ):

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

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

Gunakan kasus: privatekata kunci

Kata pengantar:

Akses ke API kelas internal dan status (kompilasi waktu saja privasi)

privateanggota kelas adalah properti konvensional pada saat run-time. Kita dapat menggunakan fleksibilitas ini untuk mengakses API internal kelas atau status dari luar. Untuk memenuhi pemeriksaan kompiler, mekanisme seperti jenis pernyataan, akses properti dinamis atau @ts-ignoredapat digunakan antara lain.

Contoh dengan jenis pernyataan ( as/ <>) dan anytugas variabel yang diketik:

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 bahkan memungkinkan akses properti dinamis dari privateanggota dengan jalan keluar :

class C {
  private foo = 10;
}

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

Di mana akses pribadi masuk akal? (1) tes unit, (2) situasi debugging / logging atau (3) skenario kasus lanjutan lainnya dengan kelas proyek-internal (daftar terbuka).

Akses ke variabel internal agak kontradiktif - jika tidak, Anda tidak akan membuatnya privatedi tempat pertama. Sebagai contoh, pengujian unit seharusnya berupa kotak hitam / abu-abu dengan bidang pribadi disembunyikan sebagai detail implementasi. Namun dalam praktiknya, mungkin ada pendekatan yang valid dari kasus ke kasus.

Tersedia di semua lingkungan ES

privatePengubah TS dapat digunakan dengan semua target ES. #Bidang -private hanya tersedia untuk target ES2015/ ES6atau lebih tinggi. Dalam ES6 +, WeakMapdigunakan secara internal sebagai implementasi tingkat bawah (lihat di sini ). Asli #bidang-swasta saat ini membutuhkan target esnext.

Konsistensi dan kompatibilitas

Tim mungkin menggunakan pedoman pengkodean dan aturan linter untuk menegakkan penggunaan private sebagai satu-satunya pengubah akses. Pembatasan ini dapat membantu dengan konsistensi dan menghindari kebingungan dengan #notasi bidang -private dengan cara yang kompatibel dengan mundur.

Jika diperlukan, properti parameter (singkatan penugasan konstruktor) adalah penghenti acara. Mereka hanya dapat digunakan dengan privatekata kunci dan tidak ada rencana untuk mengimplementasikannya untuk #bidang -privat.

Alasan lain

  • privatemungkin memberikan kinerja run-time yang lebih baik dalam beberapa kasus down-leveling (lihat di sini ).
  • Tidak ada metode kelas privat keras yang tersedia di TS hingga saat ini.
  • Beberapa orang menyukai privatenotasi kata kunci yang lebih baik 😊.

Perhatikan keduanya

Kedua pendekatan menciptakan semacam jenis nominal atau merek pada waktu kompilasi.

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

Juga, keduanya memungkinkan akses lintas-instance: instance dari kelas Adapat mengakses anggota pribadi dari Ainstance lain :

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

Sumber

ford04
sumber