Каковы различия между частным ключевым словом и частными полями в TypeScript? - PullRequest
27 голосов
/ 08 января 2020

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

class PrivateKeywordClass {
    private value = 1;
}

и использования # закрытых полей , предложенных для JavaScript :

class PrivateFieldClass {
    #value = 1;
}

Должен ли я предпочесть одно другому?

Ответы [ 2 ]

43 голосов
/ 08 января 2020

Закрытое ключевое слово

Закрытое ключевое слово в TypeScript - это время компиляции аннотация. Он сообщает компилятору, что свойство должно быть доступно только внутри этого класса:

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

Error on accessing a private field

Частные поля получены из JavaScript предложения , а также работают в обычном режиме JavaScript.

Emitted 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 подходит по умолчанию. Он выполняет то, что было разработано для выполнения sh и успешно используется разработчиками TypeScript в течение многих лет. И если у вас есть существующая кодовая база, вам не нужно переключать весь код на использование приватных полей. Это особенно верно, если вы не нацеливаетесь на esnext, так как JS, который TS генерирует для частных полей, может повлиять на производительность. Также имейте в виду, что закрытые поля имеют другие тонкие, но важные отличия от ключевого слова private

Однако, если вам нужно обеспечить конфиденциальность во время выполнения или выводится esnext JavaScript, чем использовать закрытые поля.

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

Другие отличия примечания

  • Личные поля не возвращаются Object.getOwnPropertyNames и аналогичными методами

  • Личные поля не сериализуются JSON.stringify

  • Существуют крайние значения важности наследования.

    Например, TypeScript запрещает объявлять частное свойство в подклассе с тем же именем, что и частное свойство в суперклассе.

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

    This с частными полями неверно:

    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 голосов
/ 11 января 2020

Варианты использования: # -приватные поля

Предисловие:

приватные и приватные во время выполнения

# -приватные поля обеспечивают компиляцию время и конфиденциальность во время выполнения, которая не является "взломанной". Это механизм, который запрещает доступ к члену вне тела класса любым прямым способом .

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 используемый код.

Внешние библиотеки

Библиотека авторы могут рефакторировать # -приватные идентификаторы, не вызывая серьезных изменений для клиентов Пользователи библиотеки с другой стороны защищены от доступа к внутренним полям.

JS API пропускает # -приватные поля

Встроенные JS функции и методы игнорируют # - частные поля. Это может привести к более предсказуемому выбору свойств во время выполнения. Примеры: Object.keys, Object.entries, JSON.stringify, for..in l oop и другие ( пример кода ; см. Также ответ Мэтта Бирнера ):

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

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

Варианты использования: private ключевое слово

Предисловие:

Доступ к внутреннему API класса и состоянию (конфиденциальность только во время компиляции)

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

Пример с утверждением типа (as / * Типизированное присвоение переменных 1082 *) и 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 даже разрешает динамический c доступ к свойству private члена с escape-hatch :

class C {
  private foo = 10;
}

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

Где может иметь смысл частный доступ? (1) модульные тесты, (2) ситуации отладки / ведения журнала или (3) другой расширенный сценарий ios с внутренними классами проекта (открытый список).

Доступ к внутренним переменным немного противоречив, иначе вы бы не сделали их private во-первых. В качестве примера, модульные тесты должны быть черными / серыми блоками с закрытыми полями, скрытыми как детали реализации. Однако на практике могут существовать подходы от случая к случаю.

Доступно во всех средах ES * Модификаторы

TS private могут использоваться со всеми целями ES. # -приватные поля доступны только для target ES2015 / ES6 или выше. В ES6 + WeakMap используется внутри как реализация нижнего уровня (см. здесь ). Собственные # -приватные поля в настоящее время требуют target esnext.

Согласованность и совместимость

Команды могут использовать правила кодирования и правила линтера, чтобы обеспечить использование private как единственного модификатор доступа. Это ограничение может помочь с согласованностью и избежать путаницы с нотацией # -приватного поля обратно совместимым способом.

При необходимости свойства параметра (сокращение назначения конструктора) являются демонстрацией стопор. Их можно использовать только с ключевым словом private, и пока нет планов для их реализации для # -приватных полей.

Другие причины

  • private может обеспечить лучшую производительность во время выполнения в некоторых случаях понижения (см. здесь ).
  • В настоящее время в TS нет доступных методов закрытого частного класса.
  • Некоторым людям лучше нравится private ключевое слово keyword.

Примечание на оба

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

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
    }
}

Источники

...