Які відмінності між приватним ключовим словом та приватними полями в TypeScript?


27

У TypeScript 3.8+, які відмінності використовують privateключове слово для позначення члена приватного:

class PrivateKeywordClass {
    private value = 1;
}

І використовуючи #приватні поля, запропоновані для JavaScript :

class PrivateFieldClass {
    #value = 1;
}

Чи варто віддати перевагу одному над іншим?


Відповіді:


43

Приватне ключове слово

Приватне ключовим словом в друкованому є компіляцією часу анотацій. Він повідомляє компілятору, що властивість має бути доступною лише всередині цього класу:

class PrivateKeywordClass {
    private value = 1;
}

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

Однак перевірку часу компіляції можна легко обійти, наприклад, відкинувши інформацію про тип:

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

privateКлючове слово також не дотримується під час виконання

Висланий JavaScript

При компілюванні TypeScript в JavaScript privateключове слово просто видаляється:

class PrivateKeywordClass {
    private value = 1;
}

Стає:

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

З цього ви можете зрозуміти, чому privateключове слово не забезпечує захисту від виконання: у створеному JavaScript це лише звичайна властивість JavaScript.

Приватні поля

Приватні поля забезпечують збереження властивостей під час виконання :

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 також видасть помилку часу компіляції, якщо ви спробуєте використовувати приватне поле поза класом:

Помилка доступу до приватного поля

Приватні поля походять від пропозиції JavaScript, а також працюють у звичайному JavaScript.

Висланий JavaScript

Якщо ви використовуєте приватні поля в TypeScript і націлюєте на більш старі версії JavaScript для свого виводу, наприклад, es6або es2018, TypeScript спробує генерувати код, який імітує поведінку приватних полів під час виконання.

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

Якщо ви орієнтуєтесь esnext, TypeScript випромінить приватне поле:

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

Який я повинен використовувати?

Це залежить від того, що ви намагаєтесь досягти.

privateКлючовим словом є штраф за замовчуванням. Він здійснює те, що було розроблено для досягнення, і успішно використовується розробниками TypeScript протягом багатьох років. А якщо у вас є існуюча база коду, вам не потрібно перемикати весь код на використання приватних полів. Це особливо актуально, якщо ви не орієнтуєтесь esnext, оскільки JS, який TS видає для приватних полів, може мати вплив на продуктивність. Також пам’ятайте, що приватні поля мають інші тонкі, але важливі відмінності від privateключового слова

Однак якщо вам потрібно застосувати приватність виконання або виводити esnextJavaScript, вам слід використовувати приватні поля.

Також майте на увазі, що конвенції організації / спільноти про використання того чи іншого також будуть розвиватися, оскільки приватні поля набувають все більшого поширення в екосистемах JavaScript / TypeScript

Інші відмінності примітки

  • Приватні поля не повертаються Object.getOwnPropertyNamesподібними методами

  • Приватні поля не серіалізуються JSON.stringify

  • Навколо спадкування є важливі крайові випадки.

    Наприклад, TypeScript забороняє оголошувати приватну власність у підкласі з тим самим іменем, що і приватна власність у надкласі.

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

    Це не вірно з приватними полями:

    class Base {
        #value = 1;
    }
    
    class Sub extends Base {
        #value = 2; // Not an error
    }
  • privateКлючове слово приватної власності без ініціалізатор НЕ згенерує оголошення властивості в випромінюваному JavaScript:

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

    Компілюється до:

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

    Тоді як приватні поля завжди генерують декларацію про властивості:

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

    Компілює до (при націленні esnext):

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

Подальше читання:


4

Користувачі: # -приватні поля

Передмова:

Compile час і час виконання конфіденційність

#-Приватні поля забезпечують час компіляції і час виконання секретність, яка не є «зламати». Це механізм запобігання доступу до члена з-за корпусу класу будь-яким прямим способом .

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.

Безпечне успадкування класу

#-приватні поля отримують унікальну сферу застосування. Ієрархії класів можуть бути реалізовані без випадкових перезаписів приватних властивостей з рівними іменами.

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"

TS компілятор, на щастя, видає помилку, коли privateвластивості загрожують перезаписом (див. Цей приклад ). Але через характер функції часу компіляції все ще можливо під час виконання, враховуючи помилки компіляції, ігноруються та / або використовуються JS-коди.

Зовнішні бібліотеки

Автори бібліотеки можуть рефакторно #-привласнити ідентифікатори, не спричиняючи неполадок зміни клієнтів. Користувачі бібліотеки з іншого боку захищені від доступу до внутрішніх полів.

API JS не містить #-приватні поля

Вбудовані функції JS та методи ігнорують #-приватні поля. Це може призвести до більш передбачуваного вибору властивостей під час виконання. Приклади: Object.keys, Object.entries, JSON.stringify, for..inпетлі та інші ( приклад коду , див також Метта Bierner в відповідь ):

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

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

Використовуйте випадки: privateключове слово

Передмова:

Доступ до внутрішнього API класу та стану (конфіденційність лише час компіляції)

privateчлени класу мають звичайні властивості під час виконання. Ми можемо використовувати цю гнучкість для доступу до внутрішнього API класу або стану зовні. Для задоволення перевірок компілятора такі механізми, як твердження типу, динамічний доступ до властивостей, @ts-ignoreможуть використовуватися серед інших.

Приклад із затвердженням типу ( as/ <>) та призначенням anyнабраної змінної:

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 навіть дозволяє динамічний доступ до власності privateчлена за допомогою виходу люка :

class C {
  private foo = 10;
}

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

Де приватний доступ може мати сенс? (1) одиничні тести, (2) налагодження / реєстрація ситуацій або (3) інші розширені сценарії випадків із внутрішніми проектами класів (відкритий список).

Доступ до внутрішніх змінних трохи суперечливий - інакше ви б їх не зробили privateв першу чергу. Для прикладу, одиничні тести повинні бути чорними / сірими полями з приватними полями, захованими як деталі реалізації. На практиці, однак, можуть бути вагомі підходи від конкретного випадку до конкретного.

Доступний у всіх середовищах ES

privateМодифікатори TS можуть використовуватися з усіма цілями ES. #-приватні поля доступні лише для target ES2015/ ES6або вище. У ES6 + WeakMapвикористовується внутрішньо як нижчий рівень реалізації (див. Тут ). Рідні #-Приватні поля в даний час вимагають target esnext.

Послідовність та сумісність

Команди можуть використовувати вказівки щодо кодування та правила лінійки, щоб забезпечити використання privateяк єдиного модифікатора доступу. Це обмеження може допомогти узгодженості та уникнути плутанини із позначенням #-приватне поле у ​​зворотно сумісному вигляді.

Якщо потрібно, властивості параметрів (скорочення призначення конструктора) - це стоп-шоу. Їх можна використовувати лише з privateключовими словами, і поки що не планується їх реалізація для #-приватних полів.

Інші причини

  • privateможе забезпечити кращу ефективність роботи в деяких випадках зниження рівня (див. тут ).
  • Наразі в TS не існує жорстких методів приватного класу.
  • Деяким люблять privateпозначення ключових слів краще 😊.

Примітка обох

Обидва підходи створюють певний номінальний або фірмовий тип під час компіляції.

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

Крім того, обидва дозволяють перехід між інстанціями: екземпляр класу Aможе отримати доступ до приватних членів інших Aпримірників:

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

Джерела

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.