Mengapa saya dapat mengakses anggota pribadi TypeScript ketika saya seharusnya tidak bisa?

108

Saya melihat implementasi anggota pribadi di TypeScript, dan saya merasa agak membingungkan. Intellisense tidak mengizinkan untuk mengakses anggota pribadi, tetapi dalam JavaScript murni, semuanya ada di sana. Ini membuat saya berpikir bahwa TS tidak mengimplementasikan anggota privat dengan benar. Ada pemikiran?

class Test{
  private member: any = "private member";
}
alert(new Test().member);
Sean Feldman
sumber
Anda bertanya-tanya mengapa IntelliSense tidak memberi Anda anggota pribadi di baris dengan alert ()?
mulai
7
Nggak. Saya bertanya-tanya mengapa TS memiliki private padahal itu hanya gula untuk intellisense, dan bukan untuk JavaScript yang dikompilasi. Kode ini dieksekusi di typescriptlang.org/Playground alerts nilai anggota pribadi.
Sean Feldman
Seperti yang disebutkan, Anda harus mendeklarasikan item sebagai variabel dalam konteks privat agar privat. Saya menebak skrip tidak melakukan ini karena bisa menjadi tidak efisien vs menambahkan ke prototipe. Ini juga mengacaukan definisi tipe (anggota pribadi tidak benar-benar bagian dari kelas)
Shane
Jika Anda menginginkan variabel privat nyata yang ada pada prototipe, itu membutuhkan beberapa overhead, tetapi saya telah menulis sebuah perpustakaan bernama ClassJS yang melakukan hal itu di GitHub: github.com/KthProg/ClassJS .
KthProg

Jawaban:

97

Seperti halnya pemeriksaan tipe, privasi anggota hanya diterapkan di dalam kompilator.

Properti pribadi diimplementasikan sebagai properti biasa, dan kode di luar kelas tidak diizinkan untuk mengaksesnya.

Untuk membuat sesuatu yang benar-benar pribadi di dalam kelas, itu tidak bisa menjadi anggota kelas, itu akan menjadi variabel lokal yang dibuat di dalam ruang lingkup fungsi di dalam kode yang membuat objek. Itu berarti Anda tidak dapat mengaksesnya seperti anggota kelas, misalnya menggunakan thiskata kunci.

Guffa
sumber
25
Bukan hal yang aneh bagi pemrogram javascript untuk meletakkan variabel lokal dalam konstruktor objek dan menggunakannya sebagai bidang pribadi. Saya terkejut mereka tidak mendukung hal seperti ini.
Eric
2
@Eric: Karena TypeScript menggunakan prototipe untuk metode alih-alih menambahkan metode sebagai prototipe di dalam konstruktor, variabel lokal dalam konstruktor tidak dapat dijangkau dari metode. Dimungkinkan untuk membuat variabel lokal di dalam pembungkus fungsi untuk kelas, tetapi saya belum menemukan cara untuk melakukannya. Namun, itu akan tetap menjadi variabel lokal, dan bukan anggota pribadi.
Guffa
40
Ini adalah sesuatu yang saya berikan masukan. Saya percaya itu harus menawarkan opsi untuk membuat Pola Modul Pengungkapan, sehingga anggota pribadi dapat tetap pribadi dan yang umum dapat diakses di JavaScript. Ini adalah pola umum dan akan memberikan aksesibilitas yang sama di TS dan JS.
John Papa
Ada solusi yang dapat Anda gunakan untuk anggota statis pribadi: basarat.com/2013/03/real-private-static-class-members-in.html
basarat
1
@BasaratAli: Itu adalah variabel statis yang tersedia di dalam metode kelas, tetapi ini bukan anggota kelas, yaitu Anda tidak mengaksesnya menggunakan thiskata kunci.
Guffa
37

JavaScript mendukung variabel privat.

function MyClass() {
    var myPrivateVar = 3;

    this.doSomething = function() {
        return myPrivateVar++;        
    }
}

Dalam TypeScript ini akan diekspresikan seperti ini:

class MyClass {

    doSomething: () => number;

    constructor() {
        var myPrivateVar = 3;

        this.doSomething = function () {
            return myPrivateVar++;
        }
    }
}

EDIT

Pendekatan ini sebaiknya hanya digunakan SEPENUHNYA jika benar-benar dibutuhkan. Misalnya jika Anda perlu menyimpan sandi sementara.

Ada biaya kinerja untuk menggunakan pola ini (tidak relevan dengan Javascript atau Typecript) dan sebaiknya hanya digunakan jika benar-benar diperlukan.

Martin
sumber
Tidakkah skrip ketikan melakukan ini SEMUA waktu dengan menyetel var _thisuntuk digunakan dalam fungsi cakupan? Mengapa Anda ragu melakukannya di ruang lingkup kelas?
DrSammyD
Tidak. Var _ini hanya referensi untuk ini.
Martin
2
Lebih tepatnya menyebutnya variabel konstruktor, bukan privat. Itu tidak terlihat dalam metode prototipe.
Roman M. Koss
1
oh ya, maaf masalahnya adalah yang lain, fakta bahwa untuk setiap contoh yang Anda buat, doSomething akan dibuat lagi, karena itu bukan bagian dari rantai prototipe.
Barbu Barbu
1
@BarbuBarbu Ya saya setuju. Ini adalah masalah BESAR dengan pendekatan ini, dan salah satu alasannya harus dihindari.
Martin
11

Setelah dukungan untuk WeakMap tersedia lebih luas, ada teknik menarik yang dirinci dalam contoh # 3 di sini .

Ini memungkinkan data pribadi DAN menghindari biaya kinerja contoh Jason Evans dengan memungkinkan data dapat diakses dari metode prototipe alih-alih hanya metode contoh.

Halaman WeakMap MDN yang ditautkan mencantumkan dukungan browser di Chrome 36, Firefox 6.0, IE 11, Opera 23, dan Safari 7.1.

let _counter = new WeakMap();
let _action = new WeakMap();
class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  decrement() {
    let counter = _counter.get(this);
    if (counter < 1) return;
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this)();
    }
  }
}
Ryan Thomas
sumber
Aku menyukainya! Pada dasarnya ini berarti menyembunyikan properti pribadi ke dalam kelas agregat. Yang paling menyenangkan adalah ... Bagaimana menambahkan dukungan untuk protectedparameter? : D
Roman M. Koss
2
@RamtinSoltani Statistik artikel terkait bahwa karena cara weakmaps bekerja, ini tidak akan mencegah pengumpulan sampah. Jika seseorang ingin lebih aman saat menggunakan teknik ini, mereka dapat mengimplementasikan kode pembuangan mereka sendiri yang menghapus kunci instance kelas dari masing-masing peta lemah.
Ryan Thomas
1
Dari halaman MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… . Sebaliknya, WeakMaps asli menyimpan referensi "lemah" ke objek utama, yang berarti bahwa mereka tidak mencegah pengumpulan sampah jika tidak ada referensi lain ke objek kunci tersebut. Ini juga menghindari pencegahan pengumpulan sampah nilai di peta.
Ryan Thomas
@RyanThomas Benar, itu adalah komentar lama yang saya tinggalkan beberapa waktu yang lalu. WeakMaps, sebagai lawan dari Maps, tidak akan menyebabkan kebocoran memori. Jadi aman untuk menggunakan teknik ini.
Ramtin Soltani
@RamtinSoltani Jadi hapus komentar lama Anda?>
ErikE
10

Karena TypeScript 3.8 akan dirilis, Anda akan dapat mendeklarasikan bidang pribadi yang tidak dapat diakses atau bahkan dideteksi di luar kelas yang memuatnya .

class Person {
    #name: string

    constructor(name: string) {
        this.#name = name;
    }

    greet() {
        console.log(`Hello, my name is ${this.#name}!`);
    }
}

let jeremy = new Person("Jeremy Bearimy");

jeremy.#name
//     ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.

Bidang pribadi dimulai dengan #karakter

Harap dicatat bahwa bidang pribadi ini akan menjadi sesuatu yang berbeda dari bidang yang ditandai dengan privatekata kunci

Ref. https://devblogs.microsoft.com/typescript/announcing-typescript-3-8-beta/

Przemek Struciński
sumber
4

Terima kasih kepada Sean Feldman untuk tautan ke diskusi resmi tentang masalah ini - lihat jawabannya untuk tautannya.

Saya membaca diskusi yang ditautkannya, dan inilah ringkasan dari poin-poin utamanya:

  • Saran: properti pribadi dalam konstruktor
    • masalah: tidak dapat mengakses dari fungsi prototipe
  • Saran: metode pribadi dalam konstruktor
    • masalah: sama seperti properti, ditambah Anda kehilangan manfaat performa dari pembuatan fungsi satu kali per kelas dalam prototipe; alih-alih Anda membuat salinan fungsi untuk setiap instance
  • Saran: tambahkan boilerplate ke akses properti abstrak dan terapkan visibilitas
    • masalah: overhead kinerja utama; TypeScript dirancang untuk aplikasi besar
  • Saran: TypeScript sudah membungkus definisi metode konstruktor dan prototipe dalam sebuah closure; letakkan metode dan properti pribadi di sana
    • masalah dengan menempatkan properti privat di closure itu: mereka menjadi variabel statis; tidak ada satu pun per contoh
    • masalah dengan menempatkan metode privat dalam penutupan itu: mereka tidak memiliki akses thistanpa semacam solusi
  • Saran: secara otomatis mengacaukan nama variabel privat
    • argumen lawan: itu adalah konvensi penamaan, bukan konstruksi bahasa. Hancurkan sendiri
  • Saran:@private Beri anotasi pada metode privat dengan begitu minifier yang mengenali bahwa anotasi dapat mengecilkan nama metode secara efektif
    • Tidak ada argumen tandingan yang signifikan untuk yang satu ini

Argumen balasan keseluruhan untuk menambahkan dukungan visibilitas dalam kode yang dipancarkan:

  • masalahnya adalah JavaScript itu sendiri tidak memiliki pengubah visibilitas - ini bukan masalah TypeScript
  • sudah ada pola yang mapan dalam komunitas JavaScript: awali properti pribadi dan metode dengan garis bawah, yang bertuliskan "lanjutkan dengan risiko Anda sendiri"
  • ketika desainer TypeScript mengatakan bahwa properti dan metode yang benar-benar pribadi tidak "mungkin", itu berarti "tidak mungkin di bawah batasan desain kami", khususnya:
    • JS yang dipancarkan bersifat idiomatik
    • Boilerplate minimal
    • Tidak ada biaya tambahan dibandingkan dengan JS OOP biasa
alexanderbird.dll
sumber
Jika jawaban ini berasal dari percakapan ini: typescript.codeplex.com/discussions/397651 -, berikan tautan: D
Roman M. Koss
1
Ya, itu percakapannya - tetapi saya menautkan ke jawaban Sean Feldman untuk pertanyaan ini , di mana dia memberikan tautannya. Karena dia berusaha menemukan tautan itu, saya ingin memberinya pujian.
alexanderbird
2

Dalam TypeScript Private, fungsi hanya dapat diakses di dalam kelas. Suka

masukkan deskripsi gambar di sini

Dan itu akan menunjukkan kesalahan ketika Anda mencoba mengakses anggota pribadi. Berikut contohnya:

masukkan deskripsi gambar di sini

Catatan: Ini akan baik-baik saja dengan javascript dan kedua fungsi dapat diakses dari luar.

Muhammad Awais
sumber
4
OP: "tetapi dalam JavaScript murni, semuanya ada di sana" - Saya rasa Anda tidak mengatasi masalah bahwa JavaScript yang dihasilkan mengekspos fungsi "pribadi" secara publik
alexanderbird
1
@alexanderbird Saya pikir dia ingin mengatakan bahwa TypeScript biasanya cukup. Saat kami mengembangkan di TypeScript, kami tetap menggunakannya dalam lingkup proyek, jadi privasi dari JavaScript bukanlah masalah besar. Karena pertama-tama, kode asli penting bagi pengembang, bukan yang ditranspilasi (JavaScript).
Roman M. Koss
1
Kecuali Anda menulis dan menerbitkan pustaka JavaScript, maka kode yang ditranspilasi itu penting
alexanderbird
jawaban Anda di luar topik.
canbax
1

Saya menyadari ini adalah diskusi yang lebih lama tetapi mungkin masih berguna untuk membagikan solusi saya untuk masalah variabel dan metode yang seharusnya pribadi dalam TypeScript "bocor" ke antarmuka publik dari kelas JavaScript yang dikompilasi.

Bagi saya, masalah ini murni kosmetik, yaitu semua tentang kekacauan visual saat variabel instance dilihat di DevTools. Perbaikan saya adalah mengelompokkan deklarasi pribadi bersama-sama di dalam kelas lain yang kemudian dipakai di kelas utama dan ditugaskan ke privatevariabel (tapi masih terlihat secara publik di JS) dengan nama seperti __(garis bawah ganda).

Contoh:

class Privates {
    readonly DEFAULT_MULTIPLIER = 2;
    foo: number;
    bar: number;

    someMethod = (multiplier: number = this.DEFAULT_MULTIPLIER) => {
        return multiplier * (this.foo + this.bar);
    }

    private _class: MyClass;

    constructor(_class: MyClass) {
        this._class = _class;
    }
}

export class MyClass {
    private __: Privates = new Privates(this);

    constructor(foo: number, bar: number, baz: number) {
        // assign private property values...
        this.__.foo = foo;
        this.__.bar = bar;

        // assign public property values...
        this.baz = baz;
    }

    baz: number;

    print = () => {
        console.log(`foo=${this.__.foo}, bar=${this.__.bar}`);
        console.log(`someMethod returns ${this.__.someMethod()}`);
    }
}

let myClass = new MyClass(1, 2, 3);

Saat myClassinstance dilihat di DevTools, alih-alih melihat semua anggota "pribadi" bercampur dengan yang benar-benar publik (yang bisa menjadi sangat berantakan secara visual dalam kode kehidupan nyata yang difaktor ulang dengan benar) Anda melihatnya dikelompokkan dengan rapi di dalam __properti yang diciutkan :

masukkan deskripsi gambar di sini

Caspian Canuck
sumber
1
Saya suka itu. Tampak bersih.
0

Berikut pendekatan yang dapat digunakan kembali untuk menambahkan properti pribadi yang tepat:

/**
 * Implements proper private properties.
 */
export class Private<K extends object, V> {

    private propMap = new WeakMap<K, V>();

    get(obj: K): V {
        return this.propMap.get(obj)!;
    }

    set(obj: K, val: V) {
        this.propMap.set(obj, val);
    }
}

Katakanlah Anda memiliki kelas di Clientsuatu tempat yang membutuhkan dua properti pribadi:

  • prop1: string
  • prop2: number

Di bawah ini adalah bagaimana Anda menerapkannya:

// our private properties:
interface ClientPrivate {
    prop1: string;
    prop2: number;
}

// private properties for all Client instances:
const pp = new Private<Client, ClientPrivate>();

class Client {
    constructor() {
        pp.set(this, {
            prop1: 'hello',
            prop2: 123
        });
    }

    someMethod() {
        const privateProps = pp.get(this);

        const prop1 = privateProps.prop1;
        const prop2 = privateProps.prop2;
    }
}

Dan jika yang Anda butuhkan hanyalah satu properti pribadi, maka itu menjadi lebih sederhana, karena Anda tidak perlu mendefinisikannya ClientPrivatedalam kasus itu.

Perlu dicatat, bahwa untuk sebagian besar, kelas Privatehanya menawarkan tanda tangan yang dapat dibaca dengan baik, sedangkan penggunaan langsung WeakMaptidak.

vitaly-t
sumber