Поглед към JavaScript-константата „this“ и как да я използвате с TypeScript

Функциите са малки блокове код, които приемат вход и връщат изход или имат странични ефекти, което означава, че if променя променливи извън функцията. Имаме нужда от функции за организиране на кода в малки блокове, които могат да се използват многократно. Без функции, ако искаме да изпълним отново част от кода, трябва да го копираме на различни места. Функциите са критични за всяка програма на TypeScript. В тази част от поредицата продължаваме да разглеждаме различни части от функциите на TypeScript, включително как да се справяме с обекта this с TypeScript и функциите за претоварване.

Този обект

Ако this е посочено в обикновената функция, декларирана с ключовата дума function, обектът this не е зададен вътре във функция със стрелка към функцията, която има this вътре. Ако функция със стрелка е вътре в конструктор, тогава this се задава на новия обект. Ако не е вътре в обект, тогава this вътре във функцията стрелка е undefined в строг режим. this ще бъде зададено на основния обект, ако функцията стрелка е вътре в обект. Винаги обаче получаваме window обекта, ако се позоваваме на this във функция със стрелка. Например следният код няма да се компилира и изпълни, тъй като TypeScript не позволява на нашия код да има глобалната променлива като стойност за this:

const fn = () => this
console.log(fn());

Получаваме обекта window, който се регистрира, когато console.log се изпълнява. По същия начин, ако изпълним това:

let obj = {}
obj.f = () => {
  return this;
};
console.log(obj.f());

Получаваме същото, което получавахме преди. Това е в контраст с традиционните функции, декларирани с ключовата дума function. Нека да видим какво се случва, ако заменим функциите по-горе с традиционни функции в кода по-горе, както в следния код:

const fn = function() {
  return this
};
console.log(fn);

Сега, ако имаме включен флаг noImplicitThis в нашия tsconfig.json, получаваме следната грешка от компилатора на TypeScript: 'this' implicitly has type ‘any’ because it does not have a type annotation.(2683)

Ние сме по-близо до това да накараме нашия код да работи, но той все още няма да се компилира. За да коригираме тази грешка, трябва да поставим фалшив параметър, this: void, в сигнатурата на функцията, както в кода по-долу:

const fn = function(this: void) {
    return this;
};
console.log(fn);

С горния код правим променливата this неизползваема. Ако искаме да го използваме за нещо, можем да добавим явен тип за this вместо void, за да го накараме да направи нещо. Например, ако искаме да направим обект конструктор, можем да напишем нещо като следния код:

В кода по-горе създадохме интерфейс, наречен Person, за да добавим тип данни към обекта this във функцията greet в обекта person. Когато извикаме функцията greet, параметърът this се игнорира, тъй като сме го поставили като първи параметър. TypeScript разглежда само типа this в параметъра и няма да очаква от нас да извикаме функцията greet чрез предаване на аргумент за this. След като дефинираме обекта person, можем да присвоим стойности на свойствата name и age извън него. Вече изпълнихме изискванията, изброени в интерфейса, когато дефинирахме обекта person, но трябва също така да му присвоим някаква стойност на значението, за да можем да използваме функцията greet.

След това, когато изпълняваме, изпълняваме console.log в последния ред на примера по-горе, получаваме Hi Jane. You’re 20 years old. Успешно създадохме тип данни за this, така че няма да има никаква неяснота относно стойността на this. Това помага на разработчиците да разберат какво има this в кода, тъй като това е един от най-объркващите аспекти на JavaScript. В обикновен JavaScript this може да приема различни стойности в зависимост от това къде се намира ключовата дума this в кода. В традиционните функции стойността на this ще бъде самата функция. Стрелковите функции не променят стойността на this, така че каквото и да е било отвън, е същото като каквото и да е вътре в стрелковата функция.

С TypeScript не можем да използваме this и традиционните функции за създаване на класове. Например, ако напишем:

Тогава ще получим грешка Cannot find name ‘Person’. Did you mean ‘person’?(2552) и кодът няма да се компилира. TypeScript не ни позволява да използваме традиционни функции като класове. За да използваме make класове, трябва да използваме синтаксиса class.

„това“ параметри в обратните извиквания

За функциите за обратно извикване, използвани за слушатели на събития, функциите за обратно извикване, които предаваме, трябва да бъдат въведени в интерфейс. Например, за да зададем типа персонализирани компоненти за контрол на входа, можем да напишем нещо като Tthis:

interface InputElement {
  addKeyUpListener(onclick: (this: void, e: Event) => void): void;
}

След това, когато хората, които използват контролата, могат да напишат нещо подобно, за да се изпълнява:

Какво ще стане, ако разработчиците, които използват библиотеката, се опитат да препратят към this, както в следния код?:

В този случай компилаторът на TypeScript ще издаде грешка, тъй като this е маркиран с типа void в интерфейса InputElement.

Функционални претоварвания

Претоварването на функция е създаване на функции с едно и също име, но с различни сигнатури. Това не е разрешено в JavaScript, тъй като функциите са обекти и не можем да предекларираме един и същ обект няколко пъти. Въпреки това, тъй като TypeScript е строго типизиран, което е обратното на динамичната природа на JavaScript, той трябва да намери начин да приспособи динамичните аспекти на JavaScript. За да направим това, TypeScript ни предоставя начин за претоварване на функции с различни сигнатури. За да претоварим функциите с TypeScript, просто трябва да напишем множество функционални подписи с едно и също име, преди да дефинираме действителната функция с това име. Например, можем да напишем нещо подобно:

function getPerson(person: { name: string, age: number }): { name: string, age: number };
function getPerson(person: { name: string }): { name: string };
function getPerson(person: any): any {
  return person;
}

С кода по-горе имаме функция getPerson, която може или да приеме обект със свойствата name и age, или само свойството name. Върнатият тип, който е след двоеточието, може да бъде или обект със свойствата name и age, или обект само със свойството name. Това е, което имаме в първите три реда на примера с код по-горе, където просто дефинираме подписите, които искаме за нашата getPerson функция.

След това в действителната дефиниция на функция getPerson имаме действителната дефиниция на функция. Ние отбелязваме типа на параметъра и връщаме като any, което е ОК, защото вече сме дефинирали параметрите и типовете връщане, които getPerson приема и съответно връща. Можем да извикаме getPerson, както е в кода по-долу:

console.log(getPerson({ name: 'Jane', age: 20 }));
console.log(getPerson({ name: 'Jane' }));

От console.log твърденията по-горе получаваме:

{name: "Jane", age: 20}
{name: "Jane"}

Ако се опитаме да го извикаме с аргумент, който не сме дефинирали в подписа като: console.log(getPerson({})), получаваме грешка No overload matches this call.

Основният начин за работа с обекта this в традиционните функции е да го предадете като първи параметър на функция и след това да му зададете тип, като дефинирате интерфейс за типа. TypeScript ще игнорира параметъра this и ще го третира така, сякаш не съществува. Използва се само за задаване на типа данни this. Можем да претоварваме функции в TypeScript, което не е разрешено в JavaScript. За да направим това, просто дефинираме различни подписи за функцията и им даваме едно и също име, след което дефинираме функцията със същото име, след като сме дефинирали подписите, които нашата функция ще приеме.