Галоўная / Артыкулы

Закрыцце проты прыватных калянкаў проты #: Чаго заходзіць кожны ў TypeScript і JavaScript

TypeScript-аўтарскія атрыбуты ўпэўнююцца пад час кампілявання, атрыбуты хэша прыменяюцца пад час выканання, а воблакзы захопляюць лексічны дыяпазон. Поручнае параблікуванне таго, калі самэ ўпэўнюецца кожны з іх.

Выдавец: REACTAPP.TOP7 хв чытання1514 слоў

Крыніцы: Closures vs Private in TypeScript: If We Have private and #, Why Do We Still Need Closures?

У TypeScript ёсць атрыбут private, а у JavaScript — сімвал #, таму цякава запытанне: чаму для захавання зменных все ж такі трэбуюцься закрытасі. Адказ здаецца явным, пакуль не пераглянеш, як JavaScript працюе пад час выканання, і тады все стае цякавей. Закрытасі з’яўляюцца пазней. Спачатку, што на самай працы робіць private у TypeScript?

TypeScript private

Возьмім клас з адной прыватной власнасцю:

class User {
	private secret: string = "hidden";
}

Цяпер створыце экземпляр і прачытайце атрыбут за межамі класу:

const me = new User();
console.log(me.secret);

Ключовая фраза — гэта:

console.log(me.secret);

Кампайляр tsc адхарае яго з адказом працоўнай памяткі, таму зменная выглядае як захаваная. Але проблема ў тым, што private ў першую чаргу являе сабою функцыя сістэмы тыпаў. Калі TypeScript ператвараецца ў JavaScript, гэта ключавая слова не застаёцца як механізм прыватнасці пад час выканання. Штосьць такое:

class User {
    private secret: string = "hidden";
}

Можа выдавацца прыблізна так:

class User {
    constructor() {
        this.secret = "hidden";
    }
}

У тым моманце JavaScript не ведае, што secret павінен быць прыватным, адколькі ён — звычная власнасць. Таму:

const me = new User();
console.log(me.secret);

Можна распісаць:

hidden

Разлік мае значэнне. private паведамляе кампайлеру, што код, які выкарыстоўвае гэты тып, не можа зменяць атрыбут. Аднак гэта не паведамляе час адкладання выканання, што атрыбут не можа быць доступным, і гэтыя два пункты ўсё абоце розныя.

Што зменяе #

TypeScript таксама падтрымляе вбудованыя прыватныя поля класаў у JavaScript:

class User {
    #secret: string = "hidden";
}

Прагненне зараз прачытаць поле зза меж праменяе іншым спосабам:

const me = new User();console.log(me.#secret);

Гэта не простаў толькі паветарань кампайляра. Поле таксама ў стані запуску ёсць прыватным, адтолькі сінтаксіс # стварае справжнее прыватнае поле класу. Чытанне яго зза меж класу ёсць незаконным:

console.log(me.#secret);

і чытанне простай атрыбута з тым жа іменем таксама не дае да яго запрацавіць:

console.log(me.secret);

Адзін раз #secret — гэта не звычная атрыбута пад назвам secret. Можа здавацца прыемным уявляць яго як роўнак ключа-строкі:

me["#secret"]

Але гэта не працюе. # не ўтварае правіл атрыбутавання імен, таму што час выканання мае рэальная канцепцыя прыватных полей. Таму у нас ёсць два аднальнія элемента. Першы з іх у пераважным чысла разваўляецца за дапамогою сістэмы тыпаў:

private secret: string;

а другі — за семантыкай JavaScript:

#secret: string;

Дзе падыходзяць клаусуры

Да таго, як з’явіліся прыватныя поля класаў, распрацоўшчыкам усё рава былі патрэбны даныя, да якіх не можна было прыйсці зза меж класа, і аднам з найэфектывнейшых інструментаў, якія ужо існавалі ў мове, была клаусура. Ёсць функцыя-фабрыка:

function createUser() {
    let secret = "hidden";

Ён вяртае об’ект, які выклікае адзін метод-дасягальнік:

    return {
        getSecret() {
            return secret;
        }
    };
}const user = createUser();console.log(user.getSecret());

Выклік гэтага аксесара выведае:

hidden

Зверніце ўвагу, чаго не хапае. Не існуе такога доступу да атрыбутаў:

user.secret

Адаптаваны об’ект нават не мае атрыбута secret. Зменная знаходзіцца ў лексычным сераўе, створанам калі выконвалася функцыя createUser(), а адаптаваная функцыя getSecret() пам’ятае гэты сераў. Гэта і ёсць сутніснае для клозуры: функцыя застаецца з можлівасцю доступу да зменных з таго простора імен, дзе яна была створана, нават пасля таго, як зовнішняя функцыя завершылася. Пасля гэтага вызову:

const user = createUser();

Зовнішня функцыя завершана. Зазвычай можна было б спакаваць яе локальную зменную:

let secret = "hidden";

знікнуць. Адзначыце, getSecret() заўсёды можа да яго дасягнуць, таму што ён абмежваеся secret, звядзе гэта імя і падыходзіць.

Прыватны стан без яго адкрыцьця

Клозуры могу зберагаць прыватны стан без адкрытых зменных. Калькулятор — гарны прыклад:

function myCounter() {
    let count = 0;
    return {
        increment() {
            count++;
        },        getCount() {
            return count;
        }
    };
}const counter = myCounter();counter.increment();
counter.increment();console.log(counter.getCount());

Калі два разы вызваць метод адбавлення, а пасля запытацца пра колькісць, будзе выведзена:

2

У той час як гэта не працюе:

console.log(counter.count);

Таму што count не ўтвараеяць атрыбута кантэра. Гэта зменная, якая знаходзіцца ў захаванай лексычной асяроддзі. Об’ект задае спосаб дзейства, а фактычны стан зберагаецца ў іншым месцы — гэта форма інкапсулявання, створаная за допамогай лексычнага скоупінгу.

Чы не проста # лепш?

Не павінна. Яны рашаюць супакоўаныя проблемы, але не ёсць адной і той жа функцыяй. Ось версія класу:

class User {
    #secret = "hidden";
    getSecret() {
        return this.#secret;
    }
}

У яму #secret ўособліва прыватная стан класа, якай належыць об’екту, і час выканання знае пра яго. У версіі з закрыццем:

function createUser() {
    let secret = "hidden";
    return {
        getSecret() {
            return secret;
        }
    };
}

Зменная ўзагалі не ўтварае часткі аб’екта і знаходзіцца ў суседнім лексычным сераўысе. Механізмы ў яе і аб’екта прыродна разныя. Поле # паведамляе, што аб’ект мае прыватнае поле, тады як замыканне адзначае, што функцыя можа выкарыстоўваць стан з суседньага кантэкста. Гэта разлік вплывае на дызайн API.

Стан, які дзеліцца між функцыямі

Замыканні даўаюць не толькі можлівасць прыватнасці: калькі функцый можа дзеліцца аднім элементам стану. Прыкладам можа служыць банкавы рахунак:

function createBankAccount(initialBalance) {
    let balance = initialBalance;
    return {
        deposit(amount) {
            balance += amount;
        },        withdraw(amount) {
            balance -= amount;
        },        getBalance() {
            return balance;
        }
    };
}

Яго выкарыстоўванне выглядае так:

const account = createBankAccount(1000);
account.deposit(500);console.log(account.getBalance());

і рэзультат такі:

1500

Няма публічнай масы дадзеных даўэй, якую можна прачытаць:

account.balance

deposit(), withdraw() і getBalance() усе выкарыстоўваюць тую ж адну і тую ж зменную:

balance

Тое ўсьмілыя адказы на пытанні, чаму так важлівая роля закрыценняў у JavaScript. Жытыкі не толькі ўтлакі для хавання змянюварыяў, але і спосаб прытрымання стаці ў межах вызоў.

Корэктыва першыя падзымлівыяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяя

function createMultiplier(x) {
    return function (y) {
        return x * y;
    };
}
const double = createMultiplier(2);console.log(double(10));

Хутчэй, функцыя, якая вяртаецца, пам’ятае x, і гэта ёсць клозура. Той жа механізм вікорыстоўваецца для стварэння прыватнага стану, фабрык, калбэкаў, апрацоўшчыкаў запуску адбывання, мемаізацыі і ўсё іншае.

Ментальная мадэль трох

Для TypeScript private:

class User {
    private secret = "hidden";
}

Гэта можна спрыяць як сістэма тыпаў, якая не дазволяе апусціцца на элемент. Для JavaScript #private:

class User {
    #secret = "hidden";
}

Гэта означае, што час выканання спрацоўвае з гэтым полем як з сапраўдна прыватным. Для клозуры:

function createUser() {
    let secret = "hidden";
    return {
        getSecret() {
            return secret;
        }
    };
}

Гэта можна розумець як функцыю, якая пам’ятае зменныя з таго прастора імен, дзе яна была створаная. Гэта ўсё, што важна знаты. Закрыцця не ўстарэлі, навет пасля появы #, адтолькі #private — гэта функцыя, створаная для прыватных полей класаў, тады калі закрыцця ёсць наследкам лексычнага прастора імен і функцый першага класу, і яны служыць багатым цілям, неякія не стосуюцца прыватнасці.

У сутнасці

Заставаецца ўсё ж адна тонкая дэталь. Было б некорэктна казаць, што private і # абодва стаюць звычнымі власнасцямі JavaScript. Гэта стосуецца private у TypeScript у звычным выходным фармаце, але не стосуецца натыўных полей #private:

class User {
    #secret = "hidden";
}

Этам выкарыстоўваецца фічыра часу запуску. Залежна ад меты кампілявання, TypeScript можа застаўіць сінтаксіс альбо прызначыць яго на ніжэйшы ранг для старэйшаг JavaScript, пры тым семаніка яго ўсё раве выклекчвае паведанне прыватных полей. Таму не трэба класіфікаваць private і #private ў аднай катэгорыі: яны выглядаюць падобна ў TypeScript, але іх рэалізацыя разная, а воблакжы — гэта зусім іншы механізм.

Тады чы працоўнаюць воблакжы ўсё ж такі?

Так, і лепшы вопыт, чым «чаму нам патрэбны клозуры, незважаючы на private і #», — гэта якія проблемы рашае кожны з эых механізмаў. Кожны з іх задовольняе разныя патрэбны. private обмежвае доступ унутры пераканальніка тыпаў, #private дазволяе самаму часу выканання прыменяць правіла ўтрымання «закрытых» поль, а клозуры дазволяюць функцыям продовжваць выкарыстоўваць простор імен, у якім яны былі створаны. Калі гэта стане ясным, клозуры перэстануць выглядаць як стары спосаб разв’язкі проблемы з прыватнымі зменнымі і пакажуць свою справжню ролю як базовы элемент модэлі програмавання JavaScript. Выучэнне новейшай функцыяй не робіць старыяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяяя

  • TypeScript Private Fields vs # Syntax: Compile-Time or Runtime Privacy — Поручаець модыфікатор private у TypeScript, які працюе ў часа компілявання, з полямі # у ECMAScript, якое захоўвае прыватнасць у часа выканання, каб памагчы выбраць наяўны стратэгію інкапсулявання для кодавых баз 2026 года.
  • JavaScript’s Real Blueprints: Prototypes, Delegation, and Functions — Чаму класы ў JavaScript яўляюцца толькі сінтаксічным спрыяннем протатыпам, як разлічаецца дэлегаванне ад класычнага спадкування, дзе гэта не працуе, і што даёт функцыянальны стыл у JavaScript.