Почему я могу получить доступ к закрытым членам TypeScript, когда не могу?

108

Я смотрю на реализацию частных членов в TypeScript, и это меня немного сбивает с толку. Intellisense не позволяет получить доступ к закрытому члену, но в чистом JavaScript все это есть. Это заставляет меня думать, что TS неправильно реализует закрытые члены. Есть предположения?

class Test{
  private member: any = "private member";
}
alert(new Test().member);
Шон Фельдман
источник
Вам интересно, почему IntelliSense не предоставляет вам закрытого члена в строке с alert ()?
esrange 03
7
Нет. Мне интересно, почему у TS есть приват, когда это только сахар для intellisense, а не для JavaScript, в который он компилируется. Этот код, выполняемый в typescriptlang.org/Playground, предупреждает значение частного члена.
Шон Фельдман
Как уже упоминалось, вы должны объявить элементы как переменные в частном контексте, чтобы сделать их частными. Я предполагаю, что машинописный текст не делает этого, потому что он может быть неэффективным по сравнению с добавлением к прототипу. Это также мешает определению типа (частные члены на самом деле не являются частью класса)
Шейн
Если вам нужны настоящие частные переменные, существующие в прототипе, это потребует некоторых накладных расходов, но я написал библиотеку под названием ClassJS, которая делает именно это на GitHub: github.com/KthProg/ClassJS .
KthProg

Ответы:

98

Как и при проверке типов, конфиденциальность членов обеспечивается только внутри компилятора.

Частное свойство реализовано как обычное свойство, и коду вне класса не разрешен доступ к нему.

Чтобы сделать что-то действительно частным внутри класса, это не может быть членом класса, это будет локальная переменная, созданная внутри области действия функции внутри кода, который создает объект. Это означало бы, что вы не можете получить к нему доступ как к члену класса, то есть используя thisключевое слово.

Гуффа
источник
25
Однако для программиста javascript нет ничего необычного в том, чтобы поместить локальную переменную в конструктор объекта и использовать ее как частное поле. Я удивлен, что они не поддержали что-то подобное.
Эрик
2
@Eric: Поскольку TypeScript использует прототип для методов вместо добавления методов в качестве прототипов внутри конструктора, локальная переменная в конструкторе недоступна из методов. Возможно, есть возможность создать локальную переменную внутри функции-оболочки для класса, но я еще не нашел способа сделать это. Однако это все равно будет локальная переменная, а не частный член.
Guffa
40
Это то, о чем я рассказываю. Я считаю, что он должен предлагать возможность создания шаблона Revealing Module, чтобы частные члены могли оставаться частными, а общедоступные были доступны в JavaScript. Это общий шаблон, обеспечивающий одинаковую доступность в TS и JS.
Джон Папа
Есть решение, которое вы можете использовать для частных статических членов: basarat.com/2013/03/real-private-static-class-members-in.html
basarat
1
@BasaratAli: это статическая переменная, которая доступна внутри методов класса, но не является членом класса, т.е. вы не получаете к ней доступ с помощью thisключевого слова.
Guffa
37

JavaScript поддерживает частные переменные.

function MyClass() {
    var myPrivateVar = 3;

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

В TypeScript это можно выразить так:

class MyClass {

    doSomething: () => number;

    constructor() {
        var myPrivateVar = 3;

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

РЕДАКТИРОВАТЬ

Этот подход следует использовать ВЗАИМОДЕЙСТВИТЕЛЬНО там, где это абсолютно необходимо. Например, если вам нужно временно кэшировать пароль.

Использование этого шаблона связано с затратами на производительность (не имеет отношения к Javascript или Typescript), и его следует использовать только в случае крайней необходимости.

Мартин
источник
Разве машинописный текст не делает это ВСЕГДА, настраивая его var _thisдля использования в функциях с ограниченным объемом? Почему у вас есть сомнения, делая это в области класса?
DrSammyD
Нет. Var _this просто ссылка на это.
Мартин
2
Точнее называть их переменными конструктора, а не закрытыми. Их не видно в методах-прототипах.
Роман М. Koss
1
о да, извините, проблема была в другой, потому что для каждого созданного вами экземпляра doSomething будет создаваться снова, потому что он не является частью цепочки прототипов.
Barbu Barbu
1
@BarbuBarbu Да, я согласен. Это БОЛЬШАЯ проблема с таким подходом, и это одна из причин, по которой его следует избегать.
Мартин
11

Как только поддержка WeakMap станет более доступной, появится интересная техника, подробно описанная в примере №3 здесь .

Он позволяет использовать частные данные и позволяет избежать затрат на производительность, как в примере Джейсона Эванса, позволяя доступ к данным из методов прототипа, а не только из методов экземпляра.

На связанной странице MDN WeakMap указана поддержка браузерами Chrome 36, Firefox 6.0, IE 11, Opera 23 и 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)();
    }
  }
}
Райан Томас
источник
Мне понравилось! В основном это означает скрытие частных свойств в агрегированном классе. Самое интересное будет ... Как насчет добавления поддержки protectedпараметров? : D
Роман М. Косс
2
@RamtinSoltani В связанной статье статистика говорит, что из-за того, как работают слабые карты, это не предотвратит сборку мусора. Если кто-то хочет быть в большей безопасности при использовании этого метода, он может реализовать свой собственный код удаления, который удаляет ключ экземпляра класса из каждой из слабых карт.
Райан Томас
1
Со страницы MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… . Напротив, собственные WeakMaps содержат «слабые» ссылки на ключевые объекты, что означает, что они не предотвращают сборку мусора в случае, если не будет другой ссылки на ключевой объект. Это также позволяет избежать предотвращения сбора мусора значений на карте.
Райан Томас
@RyanThomas Верно, это был старый комментарий, который я оставил некоторое время назад. WeakMaps, в отличие от Maps, не вызывает утечки памяти. Так что использовать эту технику безопасно.
Рамтин Солтани
@RamtinSoltani Так удалите свой старый комментарий?>
ErikE
10

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

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.

Частные поля начинаются с #символа

Обратите внимание, что эти частные поля будут отличаться от полей, отмеченных privateключевым словом

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

Пшемек Струцински
источник
4

Спасибо Шону Фельдману за ссылку на официальное обсуждение этого вопроса - см. Его ответ по ссылке.

Я прочитал обсуждение, на которое он ссылался, и вот краткое изложение ключевых моментов:

  • Предложение: частные свойства в конструкторе
    • проблемы: нет доступа из функций прототипа
  • Предложение: частные методы в конструкторе
    • проблемы: такие же, как и со свойствами, плюс вы теряете преимущество в производительности, создавая функцию один раз для каждого класса в прототипе; вместо этого вы создаете копию функции для каждого экземпляра
  • Предложение: добавить шаблон для доступа к абстрактным свойствам и обеспечить видимость
    • проблемы: большие накладные расходы на производительность; TypeScript разработан для больших приложений
  • Предложение: TypeScript уже оборачивает определения методов конструктора и прототипа в замыкание; поместите туда частные методы и свойства
    • проблемы с помещением частных свойств в это замыкание: они становятся статическими переменными; нет ни одного экземпляра
    • проблемы с размещением частных методов в этом закрытии: они не имеют доступа thisбез какого-либо обходного пути
  • Предложение: автоматически изменять имена частных переменных
    • встречные аргументы: это соглашение об именах, а не языковая конструкция. Покалечить это сам
  • Предложение: аннотируйте частные методы с помощью @privateминификаторов, которые распознают, что аннотация может эффективно минимизировать имена методов.
    • Никаких существенных возражений против этого

Общие контраргументы против добавления поддержки видимости в выдаваемый код:

  • проблема в том, что у самого JavaScript нет модификаторов видимости - это не проблема TypeScript
  • в сообществе JavaScript уже существует устоявшийся шаблон: к закрытым свойствам и методам добавляется знак подчеркивания, который гласит: «Действуйте на свой страх и риск».
  • когда дизайнеры TypeScript сказали, что по-настоящему частные свойства и методы «невозможны», они имели в виду «невозможно при наших ограничениях дизайна», а именно:
    • Выпущенный JS идиоматичен
    • Шаблон минимальный
    • Никаких дополнительных накладных расходов по сравнению с обычным JS OOP
Александр
источник
Если этот ответ был из этого разговора: typescript.codeplex.com/discussions/397651 -, укажите ссылку: D
Роман М. Косс
1
Да, это разговор, но я связался с ответом Шона Фельдмана на этот вопрос , где он дает ссылку. Поскольку он проделал работу по поиску ссылки, я хотел отдать ему должное.
alexanderbird
1

В TypeScript частные функции доступны только внутри класса. подобно

введите описание изображения здесь

И он покажет ошибку, когда вы попытаетесь получить доступ к частному члену. Вот пример:

введите описание изображения здесь

Примечание: все будет нормально с javascript, и обе функции доступны снаружи.

Мухаммад Авайс
источник
4
OP: «но в чистом JavaScript это все есть» - я не думаю, что вы решаете проблему, заключающуюся в том, что сгенерированный JavaScript публично предоставляет «частные» функции
alexanderbird
2
@alexanderbird Я думаю, он хотел сказать, что TypeScript обычно достаточно. Когда мы разрабатываем TypeScript, мы остаемся в рамках проекта, поэтому конфиденциальность JavaScript не имеет большого значения. Потому что для разработчика важен в первую очередь исходный код, а не транспилированный (JavaScript) код.
Roman M. Koss
2
Если вы не пишете и не публикуете библиотеку JavaScript, перенесенный код имеет значение
alexanderbird
ваш ответ не по теме.
canbax
1

Я понимаю, что это более раннее обсуждение, но все же может быть полезно поделиться своим решением проблемы предположительно частных переменных и методов в TypeScript, «просачивающихся» в открытый интерфейс скомпилированного класса JavaScript.

Для меня эта проблема чисто косметическая, т.е. все дело в визуальном беспорядке при просмотре переменной экземпляра в DevTools. Мое исправление - сгруппировать частные объявления вместе внутри другого класса, который затем создается в основном классе и назначается private(но все еще общедоступной в JS) переменной с именем типа __(двойное подчеркивание).

Пример:

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);

Когда myClassэкземпляр просматривается в DevTools, вместо того, чтобы видеть, как все его «частные» члены перемешаны с действительно общедоступными (что может стать очень визуально беспорядочным в правильно отрефакторинговом реальном коде), вы видите их аккуратно сгруппированными внутри свернутого __свойства:

введите описание изображения здесь

Каспийский канук
источник
1
Мне это нравится. Выглядит чисто.
0

Вот многоразовый подход для добавления правильных частных свойств:

/**
 * 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);
    }
}

Допустим, у вас есть класс, Clientкоторому нужны два частных свойства:

  • prop1: string
  • prop2: number

Вот как вы это реализуете:

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

А если все, что вам нужно, - это отдельная частная собственность, тогда это станет еще проще, потому что ClientPrivateв этом случае вам не нужно будет определять ее .

Стоит отметить, что по большей части class Privateпросто предлагает хорошо читаемую подпись, тогда как прямое использование этого WeakMapне делает.

Виталий-Т
источник