5. Функции

Функции

Функции - фундаментальные строительные блоки приложений на JavaScript. С помощью функций строятся уровни абстракции, организуются классы, видимость информации и модули приложения. В TypeScript, имеющем классы и модули, функции все равно играют ключевую роль. TypeScript также добавлет несколько новых возможностей к функциям JavaScript для того, чтобы упростить работу.

Функции

Так же как и в JavaScript, в TypeScript функции могут быть как именованными так и анонимными:
//Именованная функция
function add(x, y) {
    return x+y;
}

//Анонимная функция
var myAdd = function(x, y) { return x+y; };
Так же как и в JavaScript функции могут возвращать переменные. 


Типы возвращаемых значений

Можно добавить тип для каждого параметра и возвращаемого значения функций. TypeScript может определить тип возвращаемого значения по выражению return, поэтому тип возвращаемого значения можно не указывать.

function add(x: number, y: number): number {
    return x+y;
}

var myAdd = function(x: number, y: number): number { return x+y; };


Написание типа функции

Функция представляет из себя тип. Рассмотрим полное написание типа функции. 

var myAdd: (x:number, y:number)=>number = 
    function(x: number, y: number): number { return x+y; };

Тип функции имеет две одинаковые части: тип агрументов и тип возвращаемого значения. Когда функция пишется через полное описание типа функции, указывать эти две части обязательно. Параметры типа функции описываются как список параметров, где для каждого параметра указывается имя и тип. При этом имя указывается только для улучшения читабельности. Поэтому имена можно указать любые:

var myAdd: (baseValue:number, increment:number)=>number = 
    function(x: number, y: number): number { return x+y; };


Вторая часть - это возвращаемое значение. Тип возвращаемого значения указывается после стрелки =>. В описании типа функции указывать тип возвращаемого значения обязательно, поэтому, ксли функция не возвращает значение, необходимо указать 'void', но не оставлять тип возвращаемого значения пустым.
Важно иметь в виду, что только параметры и тип возвращаемого значения формируют тип функции.

Выведение типов

Компилятор TypeScript может сам определять тип выражения, основываясь на типах аргументов:

// myAdd имеет полное описание типа функции
var myAdd = function(x: number, y: number): number { return x+y; };

// Параметры 'x' и 'y' имеют тип number
var myAdd: (baseValue:number, increment:number)=>number = 
    function(x, y) { return x+y; };

Это называется 'контекстная типизация'. Это позволяет сократить усилия, чтобы программа была типизированной.

Необязательные параметры и параметры по умолчанию

В отличие от JavaScript, в TypeScript каждый параметр функции обязательный. Это не означает, что он не может быть 'null', при вызове функции компилятор проверяет, что указаны все параметры. Количество передаваемых в функцию параметров должно совпадать с количеством параметров, описанных в функции.

function buildName(firstName: string, lastName: string) {
    return firstName + " " + lastName;
}

var result1 = buildName("Bob");  //ошибка, мало параметров
var result2 = buildName("Bob", "Adams", "Sr.");  //ошибка, много параметров
var result3 = buildName("Bob", "Adams");  //нет ошибки

В JavaScript каждый параметр считается необязательным, и пользователи могут не указывать их. Тогда значения этих параметров будут неопределенными ('undefined'). Такой же функциональности в TypeScript можно добиться использованием '?' рядом с параметрами, которые мы хотим сделать необязательнвми. Например, если мы хотим, чтобы последний параметр был необязательным:


function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}

var result1 = buildName("Bob");  //работает
var result2 = buildName("Bob", "Adams", "Sr.");  //ошибка, много параметров
var result3 = buildName("Bob", "Adams");  //работает

Необязательные параметры должны строго следовать после обязательных.

В TypeScript можно указать значение необязательного параметра по умолчанию, если пользователь не его указал. Например, установим значение по умолчанию для последнего параметра "Smith".

function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}

var result1 = buildName("Bob");  //работает
var result2 = buildName("Bob", "Adams", "Sr.");  //ошибка, много параметров
var result3 = buildName("Bob", "Adams");  //работает

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


Необязательные параметры и параметры со значением по умолчанию тоже показывают какого они типа. Оба варианта:

function buildName(firstName: string, lastName?: string) {

и

function buildName(firstName: string, lastName = "Smith") {

одного и того же типа "(firstName: string, lastName?: string)=>string". Значение по умолчанию для необязательного параметра не указывается, а указывается только то, что параметр необязательный.

Однотипные параметры

Обязательные, необязательные и параметры со значением по умолчанию имеют кое-что общее: они представляют из себя параметр с одним значением. Иногда, необходимо работать с множеством параметров как с группой, или, возможно, заранее не известно сколько параметров функция в конечном счете принимает. В JavaScript можно работать с аргументами функции, которые представляют из себя переменные находящиеся вне тела функции.

В TypeScript аргументы можно объединить в переменную:

function buildName(firstName: string, ...restOfName: string[]) {
 return firstName + " " + restOfName.join(" ");
}

var employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");

Однотипные параметры рассматриваются как неограниченное число необязательных параметров. Можно не указывать ни одного или указать сколько угодно. Компилятор сформирует массив аргументов, которые указаны после многоточния (...), и этот массив будет доступен в тебе функции. 


Многоточние также используется в типе функции с однотипными параметрами:

function buildName(firstName: string, ...restOfName: string[]) {
 return firstName + " " + restOfName.join(" ");
}

var buildNameFun: (fname: string, ...rest: string[])=>string = buildName;

Ламбда и ключевое слово this

Как работает 'this' в функциях JavaScript это очень непростая тема для программистов использующих JavaScript. В действительности, понимание как работает this часто приходит только с опытом использования JavaScript. Так как TypeScript это расширение JavaScript, то разработчику TypeScript также необходимо изучить как использовать 'this' и как определить когда оно используется неправильно. Целая статья может быть написана на тему как работает 'this' В JavaScript. Рассмотрим только основы. 

В JavaScript 'this' это переменная, которая устанавливается, когда вызывается функция. Это очень полезная возможность, но она устанавливается с учетом понимания того, в каком контексте выполняется функция. Это может ввести в заблуждение, например, когда используется функция обратного вызова

Например:

var deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        return function() {
            var pickedCard = Math.floor(Math.random() * 52);
            var pickedSuit = Math.floor(pickedCard / 13);
   
            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

var cardPicker = deck.createCardPicker();
var pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

Здесь вместо сообщения будет показана ошибка. Потому что используется 'this' созданный в функции 'createCardPicker', который окажется установленный в 'window', а не на объект 'deck'. Это получается в результате вызова функции 'cardPicker()'. Здесь не используется динамическое связывание для 'this' как это делается для Window. (надо иметь в виду: в строгом режиме, this будет 'undefined', а не window).

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

Чтобы это исправить, перепишем функцию так, чтобы использовалась лямда ( ()=>{} ). Тогда 'this' будет автоматически установлен, когда функция создана, а не когда она вызывается:

var deck = {
    suits: ["hearts", "spades", "clubs", "diamonds"],
    cards: Array(52),
    createCardPicker: function() {
        // Notice: the line below is now a lambda, allowing us to capture 'this' earlier
        return () => {
            var pickedCard = Math.floor(Math.random() * 52);
            var pickedSuit = Math.floor(pickedCard / 13);
   
            return {suit: this.suits[pickedSuit], card: pickedCard % 13};
        }
    }
}

var cardPicker = deck.createCardPicker();
var pickedCard = cardPicker();

alert("card: " + pickedCard.card + " of " + pickedCard.suit);

Для большего понимания работы 'this' почитайте Yahuda Katz's Understanding JavaScript Function Invocation and “this”.

Перегрузка

JavaScriptпо своей сути очень динамический язык. И это не редкость, когда функция возвращает разные типы объектов, в зависимости от того, какие параметры в нее были переданы.

var suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x): 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") {
        var pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        var pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

var myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
var pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

var pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

Здесь функция 'pickCard' возвращает разные вещи в зависимости от того, что в нее передано.Если в нее передан deck, она вернет card. Если в нее передан card, то указывается какой именно card передан. Но как это описывается в системе типов?

Ответ - использовать перегрузку. В зависимости от параметров компилятор выбирает какую вызывать функцию. Например:

var 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 {
    // 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") {
        var pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        var pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

var myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
var pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

var pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

При этом компилятор для определения подходящей функции будет пробовать их попорядку. И как только найдет функцию с подходящими параметрами, будет считать, что вызывается именно она. Это надо учитывать при описании порядка функций.

Комментариев нет :

Отправить комментарий

Примечание. Отправлять комментарии могут только участники этого блога.