Функции
Эта страница руководства заменена, перейдите на новую страницу
Функции являются основополагающим строительным блоком любого приложения на JavaScript. С их помощью вы создаете слои абстракции, имитируя классы, скрывая информацию и создавая модули. В TypeScript, хотя есть классы, пространства имен и модули, функции по-прежнему играют ключевую роль в описании того, как делать вещи. TypeScript также добавляет некоторые новые возможности к стандартным функциям JavaScript, чтобы сделать их более удобными в работе.
Функции
Для начала, как и в JavaScript, функции TypeScript могут быть созданы как именованные функции, так и как анонимные функции. Это позволяет вам выбрать наиболее подходящий подход для вашего приложения, будь то создание списка функций в API или одноразовой функции для передачи другой функции.
Для быстрого повторения, как эти два подхода выглядят в JavaScript:
// Named function
function add(x, y) {
return x + y;
}
// Anonymous function
let myAdd = function (x, y) {
return x + y;
}; Точно так же, как и в JavaScript, функции могут ссылаться на переменные за пределами тела функции. Когда они это делают, говорят, что они захватывают эти переменные. Хотя понимание того, как это работает (и компромиссы при использовании этой техники) выходит за рамки этой статьи, прочное понимание того, как работает этот механизм, является важной частью работы с JavaScript и TypeScript.
let z = 100;
function addToZ(x, y) {
return x + y + z;
} Типы функций
Типизация функции
Добавим типы к нашим простым примерам из предыдущих разделов:
function add(x: number, y: number): number {
return x + y;
}
let myAdd = function (x: number, y: number): number {
return x + y;
}; Мы можем добавить типы к каждому из параметров, а затем к самой функции, чтобы добавить тип возвращаемого значения. TypeScript может определить тип возвращаемого значения, посмотрев на операторы return, поэтому во многих случаях мы можем его также опустить.
Запись типа функции
Теперь, когда мы типизировали функцию, давайте запишем полный тип функции, рассмотрев каждую часть типа функции.
let myAdd: (x: number, y: number) => number = function (
x: number,
y: number
): number {
return x + y;
}; Тип функции имеет те же две части: тип аргументов и тип возвращаемого значения. При записи всего типа функции обе части необходимы. Мы записываем типы параметров, как список параметров, присваивая каждому параметру имя и тип. Это имя используется только для повышения читабельности. Вместо этого мы могли бы записать:
let myAdd: (baseValue: number, increment: number) => number = function (
x: number,
y: number
): number {
return x + y;
}; До тех пор, пока типы параметров совпадают, это считается допустимым типом для функции, независимо от имен, которые вы даете параметрам в типе функции.
Вторая часть — тип возвращаемого значения. Мы делаем ясным, какой тип является типом возвращаемого значения, используя стрелку (=>) между параметрами и типом возвращаемого значения. Как упоминалось ранее, эта часть является обязательной для типа функции, поэтому, если функция не возвращает значение, вы должны использовать void вместо того, чтобы ее опускать.
Обратите внимание, что только параметры и тип возвращаемого значения составляют тип функции. Захваченные переменные не отражаются в типе. По сути, захваченные переменные являются частью «скрытого состояния» любой функции и не составляют ее API.
Выведение типов
Используя пример, вы можете заметить, что компилятор TypeScript может определить тип, даже если у вас есть типы только с одной стороны уравнения:
// The parameters 'x' and 'y' have the type number
let myAdd = function (x: number, y: number): number {
return x + y;
};
// myAdd has the full function type
let myAdd2: (baseValue: number, increment: number) => number = function (x, y) {
return x + y;
}; Это называется «контекстной типизацией», формой вывода типов. Это помогает сократить количество усилий, необходимых для сохранения типизации вашей программы.
Необязательные и параметры по умолчанию
В TypeScript каждая функция предполагается обязательной. Это не означает, что ей нельзя передать null или undefined, но компилятор проверяет, что пользователь предоставил значение для каждого параметра при вызове функции. Компилятор также предполагает, что эти параметры являются единственными параметрами, которые будут переданы функции. Короче говоря, количество аргументов, передаваемых функции, должно соответствовать количеству ожидаемых параметров.
function buildName(firstName: string, lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right В JavaScript каждый параметр является необязательным, и пользователи могут опустить их по своему усмотрению. В этом случае их значение undefined. Мы можем получить эту функциональность в TypeScript, добавив ? в конце параметров, которые мы хотим сделать необязательными. Например, давайте сделаем необязательным последний параметр фамилии из приведенного выше примера:
function buildName(firstName: string, lastName?: string) {
if (lastName) return firstName + " " + lastName;
else return firstName;
}
let result1 = buildName("Bob"); // works correctly now
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // ah, just right Все необязательные параметры должны следовать за обязательными параметрами. Если бы мы хотели сделать имя необязательным, а не фамилию, нам нужно было бы изменить порядок параметров в функции, поместив имя в конец списка.
В TypeScript мы также можем установить значение, которое параметр получит, если пользователь не предоставит его или передаст undefined вместо него. Эти параметры называются параметрами с инициализацией по умолчанию. Возьмем предыдущий пример и зададим значение по умолчанию для фамилии "Smith".
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined); // still works, also returns "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result4 = buildName("Bob", "Adams"); // ah, just right Параметры с инициализацией по умолчанию, которые следуют за всеми обязательными параметрами, считаются необязательными, и, как и необязательные параметры, их можно опустить при вызове соответствующей функции. Это означает, что необязательные параметры и параметры с инициализацией по умолчанию по умолчанию будут иметь общие типы, поэтому оба
function buildName(firstName: string, lastName?: string) {
// ...
} и
function buildName(firstName: string, lastName = "Smith") {
// ...
} имеют одинаковый тип (firstName: string, lastName?: string) => string. Значение по умолчанию lastName исчезает в типе, оставляя только то, что параметр необязательный.
В отличие от обычных необязательных параметров, параметры с инициализацией по умолчанию не обязаны следовать за обязательными параметрами. Если параметр с инициализацией по умолчанию предшествует обязательным параметрам, пользователям необходимо явно передать undefined для получения значения по умолчанию. Например, мы можем записать наш последний пример только с инициализацией по умолчанию для firstName:
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
let result1 = buildName("Bob"); // error, too few parameters
let result2 = buildName("Bob", "Adams", "Sr."); // error, too many parameters
let result3 = buildName("Bob", "Adams"); // okay and returns "Bob Adams"
let result4 = buildName(undefined, "Adams"); // okay and returns "Will Adams" Параметры rest
Обязательные, необязательные и параметры по умолчанию все имеют одну общую черту: они работают с одним параметром за раз. Иногда вам нужно работать с несколькими параметрами в группе или вы не знаете, сколько параметров в конечном итоге примет функция. В JavaScript вы можете работать с этими аргументами напрямую, используя переменную arguments , которая видна внутри тела каждой функции.
В TypeScript вы можете собрать эти аргументы в переменную:
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
// employeeName will be "Joseph Samuel Lucas MacKinzie"
let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie"); Параметры rest обрабатываются как неограниченное количество необязательных параметров. При передаче аргументов для параметра rest вы можете использовать любое количество аргументов; вы даже можете не передавать их. Компилятор создаст массив передаваемых аргументов с заданным именем после многоточия (...), что позволит вам использовать его в вашей функции.
Многоточие также используется в типе функции с параметрами rest:
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
let buildNameFun: (fname: string, ...rest: string[]) => string = buildName; this
Изучение использования this в JavaScript — это своего рода обряд посвящения. Поскольку TypeScript является надмножеством JavaScript, разработчики TypeScript также должны изучить, как использовать this и как распознать, когда оно используется неправильно. К счастью, TypeScript позволяет вам обнаружить неправильное использование this с помощью нескольких техник. Если вам нужно узнать, как работает this в JavaScript, сначала прочитайте статью Yehuda Katz’а Understanding JavaScript Function Invocation and “this”. Статья Yehuda очень хорошо объясняет внутреннюю работу this , поэтому здесь мы рассмотрим только основы.
this и стрелочные функции
В JavaScript, this — это переменная, которая устанавливается при вызове функции. Это делает ее очень мощной и гибкой функцией, но это происходит за счет того, что вам всегда нужно знать контекст, в котором выполняется функция. Это особенно запутанно, особенно при возвращении функции или передаче функции в качестве аргумента.
Давайте рассмотрим пример:
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function () {
return function () {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
},
};
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit); Обратите внимание, что createCardPicker — это функция, которая сама возвращает функцию. Если мы попытаемся запустить пример, мы получим ошибку вместо ожидаемого окна подтверждения. Это связано с тем, что this , используемое в функции, созданной createCardPicker , будет установлено в window вместо нашего объекта deck. Это происходит потому, что мы вызываем cardPicker() самостоятельно. При вызове такого уровня без метода используется window в качестве this. (Примечание: в строгом режиме this будет undefined вместо window).
Мы можем исправить это, убедившись, что функция связана с правильным this перед тем, как вернуть ее для использования позже. Таким образом, независимо от того, как она будет использоваться позже, она все еще сможет видеть исходный объект deck. Для этого мы меняем выражение функции на синтаксис стрелочных функций ECMAScript 6. Стрелочные функции захватывают this места создания функции, а не ее вызова:
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function () {
// NOTE: the line below is now an arrow function, allowing us to capture 'this' right here
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
},
};
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit); Еще лучше, TypeScript предупредит вас об этой ошибке, если вы передадите флаг noImplicitThis компилятору. Он укажет, что this в this.suits[pickedSuit] имеет тип any.
this параметры
К сожалению, тип this.suits[pickedSuit] по-прежнему any. Это потому, что this происходит из выражения функции внутри объекта-литерала. Для исправления этого вы можете предоставить явный параметр this. Параметры this — это фиктивные параметры, которые идут первыми в списке параметров функции:
function f(this: void) {
// make sure `this` is unusable in this standalone function
} Давайте добавим несколько интерфейсов к приведенному выше примеру, Card и Deck, чтобы типы были более понятными и могли использоваться повторно:
interface Card {
suit: string;
card: number;
}
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// NOTE: The function now explicitly specifies that its callee must be of type Deck
createCardPicker: function (this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
};
},
};
let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();
alert("card: " + pickedCard.card + " of " + pickedCard.suit); Теперь TypeScript знает, что createCardPicker ожидает вызова на объекте Deck. Это означает, что this теперь имеет тип Deck, а не any, поэтому noImplicitThis не вызовет ошибок.
this параметры в колбеках
Вы также можете столкнуться с ошибками с this в колбеках, когда вы передаете функции библиотеке, которая их позже вызовет. Поскольку библиотека, которая вызывает ваш колбек, вызовет его как обычную функцию, this будет undefined . С помощью некоторых усилий вы можете использовать параметры this для предотвращения ошибок в колбеках тоже. Сначала автору библиотеки нужно анотировать тип колбека с помощью this:
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
} this: void означает, что addClickListener ожидает, что onclick будет функцией, которая не требует типа this. Во-вторых, добавьте аннотацию к вызывающему коду с помощью this.
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// oops, used `this` here. using this callback would crash at runtime
this.info = e.message;
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // error! С помощью аннотации this вы делаете явным, что onClickBad должен вызываться для экземпляра Handler. Затем TypeScript обнаружит, что addClickListener требует функции, у которой есть this: void. Чтобы исправить ошибку, измените тип this:
class Handler {
info: string;
onClickGood(this: void, e: Event) {
// can't use `this` here because it's of type void!
console.log("clicked!");
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood); Поскольку onClickGood указывает свой тип this как void, допустимо передавать его в addClickListener . Конечно, это также означает, что он не может использовать this.info. Если вам нужно и то, и другое, вам нужно использовать стрелочную функцию:
class Handler {
info: string;
onClickGood = (e: Event) => {
this.info = e.message;
};
} Это работает, потому что стрелочные функции используют внешний this, поэтому вы всегда можете передавать их в то, что ожидает this: void. Недостатком является то, что для каждого объекта типа Handler создается одна стрелочная функция. Методы, с другой стороны, создаются только один раз и прикрепляются к прототипу Handler. Они используются всеми объектами типа Handler.
Перегрузки
JavaScript — по своей природе очень динамичный язык. Не редкость, когда одна функция JavaScript возвращает разные типы объектов в зависимости от формы переданных аргументов.
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: any): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 },
];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit); Здесь функция pickCard вернет две разные вещи в зависимости от того, что ввёл пользователь. Если пользователь передаёт объект, представляющий колоду, функция выбирает карту. Если пользователь выбирает карту, мы сообщаем ему, какую карту он выбрал. Но как описать это системе типов?
Ответ заключается в предоставлении нескольких типов функций для одной функции в виде списка перегрузок. Этот список будет использоваться компилятором для разрешения вызовов функций. Давайте создадим список перегрузок, описывающий, что принимает наша функция pickCard и что она возвращает.
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: { suit: string; card: number }[]): number;
function pickCard(x: number): { suit: string; card: number };
function pickCard(x: any): any {
// Check to see if we're working with an object/array
// if so, they gave us the deck and we'll pick the card
if (typeof x == "object") {
let pickedCard = Math.floor(Math.random() * x.length);
return pickedCard;
}
// Otherwise just let them pick the card
else if (typeof x == "number") {
let pickedSuit = Math.floor(x / 13);
return { suit: suits[pickedSuit], card: x % 13 };
}
}
let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 },
];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit); С этим изменением перегрузки теперь предоставляют нам проверенные по типу вызовы функции pickCard.
Для того, чтобы компилятор выбрал правильную проверку типа, он следует аналогичному процессу, что и лежащий в основе JavaScript. Он просматривает список перегрузок и, начиная с первой перегрузки, пытается вызвать функцию с предоставленными параметрами. Если он находит совпадение, он выбирает эту перегрузку как правильную. По этой причине обычно перегрузки упорядочиваются от наиболее конкретных к наименее конкретным.
Обратите внимание, что элемент function pickCard(x): any не является частью списка перегрузок, поэтому у него всего две перегрузки: одна, принимающая объект, и одна, принимающая число. Вызов pickCard с другими типами параметров приведет к ошибке.
© 2012-2023 Microsoft
Licensed under the Apache License, Version 2.0.
https://www.typescriptlang.org/docs/handbook/functions.html