Чому я можу отримати доступ до приватних членів TypeScript, коли я не маю можливості?


108

Я дивлюся на реалізацію приватних членів у TypeScript, і вважаю це трохи заплутаним. Intellisense не дозволяє отримувати доступ до приватного члена, але в чистому JavaScript це все є. Це змушує мене думати, що TS не реалізує приватних членів правильно. Будь-які думки?

class Test{
  private member: any = "private member";
}
alert(new Test().member);

Вам цікаво, чому IntelliSense не дає вам приватного члена на ряду із сповіщенням ()?
esrange

7
Ні. Мені цікаво, чому TS має приватний, коли це лише цукор для інтелігенції, а не насправді для JavaScript, який він компілює. Цей код, виконаний у typecriptlang.org/Playground, попереджає значення приватного члена.
Шон Фельдман

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

Якщо ви хочете реальні приватні змінні, які існують у прототипі, це потребує деяких накладних витрат, але я написав бібліотеку під назвою ClassJS, яка робить саме це на GitHub: github.com/KthProg/ClassJS .
KthProg

Відповіді:


97

Так само, як і при перевірці типів, конфіденційність членів забезпечується лише в межах компілятора.

Приватна власність реалізується як звичайна властивість, і код за межами класу не має доступу до неї.

Щоб зробити щось дійсно приватне всередині класу, він не може бути членом класу, це буде локальна змінна, створена всередині функції функції всередині коду, який створює об'єкт. Це означає, що ви не можете отримати доступ до нього, як член класу, тобто використовуючи thisключове слово.


25
Незвичайно для програміста javascript вводити локальну змінну в конструктор об'єктів і використовувати його як приватне поле. Я здивований, що вони не підтримали щось подібне.
Ерік

2
@Eric: Оскільки TypeScript використовує прототип для методів замість додавання методів як прототипів всередині конструктора, локальна змінна в конструкторі недоступна для методів. Можливо, можна зробити локальну змінну всередині функціональної обгортки для класу, але я ще не знайшов способу це зробити. Однак це все ще буде локальною змінною, а не приватним членом.
Гуффа

40
Це те, про що я отримував відгуки. Я вважаю, що він повинен запропонувати можливість створення шаблону модуля розкриття, тому приватні члени можуть залишатися приватними, а публічні - доступними в JavaScript. Це загальна закономірність і забезпечувала б однакову доступність у ТС та СВ.
Джон Папа

Є рішення, яке можна використовувати для приватних статичних членів: 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++;
        }
    }
}

EDIT

Цей підхід слід застосовувати БЕЗПЕЧНО тільки там, де він абсолютно необхідний. Наприклад, якщо вам потрібно тимчасово кешувати пароль.

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


Хіба машинопис не робить це ВСІМ часом, встановлюючи var _thisдля використання в масштабних функціях? Чому б вам довелося робити це в межах класу?
DrSammyD

Ні. Var _це лише посилання на це.
Мартін

2
Точніше називати їх змінними конструктора, а не приватними. Вони не видно в прототипах методів.
Роман М. Косс

1
о так, вибачте, що проблема була іншою, тим, що для кожного створеного вами екземпляра, doSomething буде створено заново, оскільки це не є частиною ланцюга прототипу.
Барбу Барбу

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 Щоправда, це був старий коментар, який я залишив деякий час тому. Слабкі карти, на відміну від Карт, не спричинять витоку пам'яті. Тож безпечно користуватися цією технікою.
Рамтін Солтані

@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ключовим словом

Реф. https://devblogs.microsoft.com/typescript/announcing-typescript-3-8-beta/


4

Дякую Шону Фельдману за посилання на офіційну дискусію з цього питання - дивіться його відповідь за посиланням.

Я прочитав дискусію, з якою він пов’язаний, і ось короткий виклад основних моментів:

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

Загальні контр-аргументи для додавання підтримки видимості в емітований код:

  • проблема полягає в тому, що сам JavaScript не має модифікаторів видимості - це не проблема TypeScript
  • у спільноті JavaScript вже встановлена ​​закономірність: префікс приватних властивостей та методів із підкресленням, який говорить: "продовжуйте на свій страх і ризик".
  • коли дизайнери TypeScript сказали, що по-справжньому приватні властивості та методи "неможливі", вони означають "неможливі в умовах наших дизайнерських обмежень", зокрема:
    • Випромінюваний JS є ідіоматичним
    • Котельня плита мінімальна
    • Ніяких додаткових накладних витрат у порівнянні зі звичайними JS OOP

Якщо ця відповідь була з цієї розмови: typecript.codeplex.com/discussions/397651 -, будь ласка, надайте посилання: D
Роман М. Косс

1
Так, це розмова - але я пов’язав відповідь Шона Фельдмана на це запитання , де він надає посилання. Оскільки він зробив роботу з пошуку посилання, я хотів дати йому кредит.
alexanderbird

2

У TypeScript приватні функції доступні лише всередині класу. Подібно до

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

І при спробі отримати доступ до приватного члена він з’явиться помилка. Ось приклад:

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

Примітка. Це буде добре з JavaScript, і обидві функції доступні зовні.


4
ОП: "але в чистому JavaScript це все є" - я не думаю, що ви вирішуєте питання про те, що створений JavaScript публічно відкриває "приватні" функції
alexanderbird

1
@alexanderbird Я думаю, що він хотів сказати, що TypeScript зазвичай достатній. Коли ми розвиваємося в TypeScript, ми залишаємося з ним у межах проекту, тому конфіденційність від JavaScript не є великою справою. Тому що, перш за все, оригінальний код має значення для розробника, а не перекладений (JavaScript).
Роман М. Косс

1
Якщо ви не пишете і не публікуєте бібліотеку 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в цьому випадку.

Варто зауважити, що здебільшого клас Privateпросто пропонує добре прочитаний підпис, тоді як безпосереднє використання цього WeakMapне робить.

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