Перейти к основному содержимому
Блокчейн23ГЛАВА 23Функции: как код перестаёт повторятьсяfn()

Функции: как код перестаёт повторяться

Прочитано0%
Квизы0/5
Тренажёрыне пройден0/1
Уровень 1 · Новичок

Зачем это нужно​

Пока программа умещается в двадцать строк, функции кажутся лишней церемонией. Проблема начинается на сотой строке: одно и то же вычисление комиссии оказывается скопированным в четырёх местах, а поменять формулу надо во всех четырёх — и в одном из них ты про неё забудешь.

Функция — это способ дать куску кода имя, чтобы дальше пользоваться именем, а не куском. Всё остальное — следствие: код становится короче, изменение делается в одном месте, а имена функций начинают работать как документация. Читать feeFor(size) понятнее, чем size * 0.001 + 0.0005, даже если формула перед глазами.

В блокчейн-коде это заметнее, чем где-либо. Подписать транзакцию, посчитать комиссию, дождаться подтверждения, разобрать ответ ноды — каждое из этих действий пишут один раз и вызывают из десятка мест. А ещё функции здесь передают как значения: «сделай вот это, когда придёт ответ» — это функция, отданная другой функции.

Глава разбирает функции полностью: как объявляются, чем три формы записи отличаются друг от друга, что такое подъём, замыкание и this, почему стрелочные функции ведут себя иначе, и какие проверки добавляет TypeScript. Все выводы и сообщения об ошибках сняты с живого запуска Node.js 24 и TypeScript 6.

Анатомия функции: от объявления до вызова​

Самая простая функция целиком:

function fee(size) {
return size * 0.001;
}

console.log(fee(1000)); // 1

Первые три строки — объявление: здесь функция описана, но ничего не выполняется. Четвёртая — вызов: здесь она работает.

Нажми на часть выражения

И вызов:

Нажми на часть выражения
ВЫЗОВ И ВОЗВРАТместо вызоваconst fee = calcFee(1000, 3)пока функция работает,эта строка ждётconsole.log(fee)тело функцииfunction calcFee(sum, pct) sum → 1000 pct → 3 return sum * pct / 100return завершает функцию сразуаргументы 1000 и 3одно значение: 30имя без скобок — это сама функция; со скобками — её результат
Вызов передаёт аргументы внутрь, функция выполняет тело и возвращает одно значение обратно в точку вызова. Остальной код на это время ждёт.

Написать имя без скобок — не ошибка, а другое действие: получится сама функция как значение. Проверить легко живым запуском:

console.log(typeof fee); // function
console.log(fee(1000)); // 1

Первая строка печатает «function», а не число, потому что вызова не было. Эта разница пригодится через несколько разделов, когда функции начнут передавать как значения.

Параметры и аргументы: два разных слова​

Слова похожие, и путать их не стоит, потому что дальше в сообщениях об ошибках встретятся оба.

  • Параметр — имя в объявлении. Существует только внутри функции, снаружи о нём никто не знает.
  • Аргумент — конкретное значение при вызове.
function pair(a, b) { return [a, b]; } // a и b — параметры
pair(1, 2); // 1 и 2 — аргументы

Параметры сопоставляются с аргументами по порядку, а не по имени. Первый аргумент попадает в первый параметр, второй — во второй. Никакой связи с именами нет: переставишь аргументы местами — функция молча посчитает не то.

Чистый JavaScript при этом на количество не смотрит вообще. Живой запуск:

console.log(pair(1)); // [ 1, undefined ]
console.log(pair(1, 2, 3)); // [ 1, 2 ]

Недостающий аргумент даёт undefined, лишний просто отбрасывается. Ошибки нет ни там, ни там — и это одна из главных причин, по которым в блокчейн-проектах пишут на TypeScript: там оба случая ловятся до запуска.

error TS2554: Expected 2 arguments, but got 1.
error TS2554: Expected 2 arguments, but got 3.

Перевод: «ожидалось 2 аргумента, получено 1» и «…получено 3».

return: что функция отдаёт наружу​

return делает две вещи одновременно: отдаёт значение в точку вызова и немедленно завершает функцию. Код после return в той же ветке не выполнится никогда.

function label(status) {
if (status === 'confirmed') {
return 'в блоке';
console.log('это никогда не напечатается');
}
return 'ещё нет';
}

Функция без return возвращает undefined — не ноль, не пустую строку, не ошибку. Живой запуск:

function nothing() {}
console.log(nothing()); // undefined

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

return без значения (return;) тоже возвращает undefined — его пишут, когда нужно просто выйти из функции досрочно.

Второй важный момент: вернуть можно ровно одно значение. Нужно несколько — упаковывают в массив или объект:

function analyze(tx) {
return { valid: tx.amount > 0, fee: tx.amount * 0.001 };
}
const { valid, fee } = analyze(tx);
Важно

Отдельная беда — return на своей строке. Из-за автоматической расстановки точек с запятой запись

return
{ valid: true };

превращается в return;, и функция вернёт undefined, а объект окажется недостижимым кодом. Ошибки не будет. Правило: значение всегда начинается на той же строке, что и return.

Значения по умолчанию, необязательные параметры и остаток​

Три приёма, которые делают функции удобнее.

Значение по умолчанию подставляется, если аргумент не передали:

function feeFor(size, rate = 0.001) { return size * rate; }
console.log(feeFor(1000)); // 1
console.log(feeFor(1000, 0.002)); // 2

Значение по умолчанию вычисляется при каждом вызове и может ссылаться на предыдущие параметры: function f(a, b = a * 2) — законная запись.

Остаточные параметры собирают всё лишнее в массив:

function sum(...nums) { return nums.reduce((a, b) => a + b, 0); }
console.log(sum(1, 2, 3)); // 6
console.log(sum()); // 0

Три точки перед именем — это оператор остатка. Такой параметр может быть только один и только последним. Обрати внимание: sum() без аргументов вернул 0, а не ошибку, — nums оказался пустым массивом.

Объект вместо длинного списка параметров. Когда параметров становится больше трёх, вызов вида send('3P...', 5, 0.001, true, false) невозможно прочитать: что означает последний false, знает только автор. Решение — передавать объект и разбирать его прямо в списке параметров:

function send({ to, amount, fee = 0.001 }) { return `${to} ${amount} ${fee}`; }
console.log(send({ to: '3P...', amount: 5 })); // 3P... 5 0.001

Фигурные скобки в списке параметров — деструктуризация: из пришедшего объекта достаются поля to, amount, fee, причём у последнего есть значение по умолчанию. Вызов теперь читается сам: каждое значение подписано именем.

Три формы записи и чем они различаются​

Одну и ту же функцию можно записать тремя способами. Разница не только в буквах.

// 1. Объявление функции
function fee(size) { return size * 0.001; }

// 2. Функциональное выражение
const fee2 = function (size) { return size * 0.001; };

// 3. Стрелочная функция
const fee3 = (size) => size * 0.001;
ТРИ ФОРМЫ ЗАПИСИобъявлениеfunction f() {}поднимается наверхвызов до строки — работаетсвой this естьвыражениеconst f = function () {}появляется на своей строкевызов до строки — ошибкасвой this естьстрелкаconst f = () => {}появляется на своей строкевызов до строки — ошибкасвоего this НЕТстрелка без фигурных скобок возвращает выражение самачтобы вернуть объект, его заворачивают в круглые скобки
Три формы записи: объявление поднимается наверх, выражение и стрелка появляются только на своей строке. Стрелка вдобавок не имеет собственного this.

Стрелочную форму разберём подробно — в современном коде она встречается чаще всех:

Нажми на часть выражения

У стрелки два варианта тела, и путаница между ними — очень частая ошибка:

const short = (x) => x * 2; // тело-выражение: результат возвращается сам
const long = (x) => { return x * 2; }; // тело-блок: return обязателен
const wrong = (x) => { x * 2; }; // фигурные скобки есть, return нет → вернёт undefined

Отдельный случай — вернуть объект. Фигурная скобка после стрелки понимается как начало блока, поэтому объект оборачивают в круглые скобки:

const objRet = (x) => ({ v: x });
console.log(JSON.stringify(objRet(2))); // {"v":2}

Практическое правило выбора: function — для функций верхнего уровня, стрелка — для коротких вставок внутри других вызовов (map, filter, обработчики). Функциональные выражения в форме const f = function () {} в новом коде почти не пишут — их вытеснили стрелки.

Подъём: почему одна форма работает раньше объявления, а другая — нет​

Живой запуск, который объясняет разницу лучше любой теории:

console.log(fee(10)); // 0.01 — работает!
function fee(x) { return x * 0.001; }

console.log(arrow(1)); // падает
const arrow = (x) => x + 1;
ReferenceError: Cannot access 'arrow' before initialization

Перевод: «нельзя обратиться к arrow до инициализации».

Причина в подъёме (hoisting). Объявления функций через function обрабатываются до выполнения кода: к моменту первой строки функция уже существует целиком. Переменная через const или let тоже «известна» с начала блока, но до своей строки находится в мёртвой зоне — обратиться к ней нельзя, и язык сообщает об этом честной ошибкой.

Практический вывод простой: пиши объявления выше использования — и вопрос подъёма перестанет тебя касаться. А ошибку Cannot access … before initialization теперь узнаешь с первого взгляда: где-то стрелочная функция вызвана раньше, чем объявлена.

Область видимости и замыкания​

Всё, что объявлено внутри функции, снаружи не существует. Это и есть область видимости: имена живут в том блоке, где созданы.

function calc() {
const inside = 42;
return inside;
}
console.log(inside); // ReferenceError: inside is not defined

Обратное неверно: изнутри функция видит имена снаружи. И вот тут начинается самое интересное — функция запоминает окружение, в котором была создана, даже когда это окружение уже отработало:

function counter() {
let n = 0;
return () => ++n;
}
const c = counter();
console.log(c(), c(), c()); // 1 2 3

Функция counter давно завершилась, а n жива и растёт: возвращённая стрелка держит её при себе. Такая пара «функция плюс запомненное окружение» называется замыканием (closure).

ЗАМЫКАНИЕвнешняя функцияfunction makeCounter() {let n = 0return () =>++nвнутренняявнешняя завершилась и исчезлауносит с собойчто осталось житьconst next = makeCounter()next() → 1next() → 2next() → 3n продолжает жить, пока жива nextснаружи к n не добраться: имени нет ни в одной видимой областивторой вызов makeCounter() создаст отдельный, свой n
Замыкание: внутренняя функция уносит с собой ссылку на переменные внешней. Внешняя функция завершилась, а её переменные продолжают жить, пока жива внутренняя.

Замыкания — не экзотика, ты уже пользовался ими в главе про циклы: разница между var и let в цикле — ровно про то, какое окружение запомнили три созданных функции. И почти каждый обработчик, переданный в map или в ожидание ответа ноды, — замыкание: он видит переменные того места, где написан.

Отвечено вопросов: 0 из 5
Чем параметр отличается от аргумента?
Что вернёт функция без return?
Что напечатает pair(1) для function pair(a, b) { return [a, b]; }?
Почему вызов стрелочной функции до её объявления даёт ReferenceError, а вызов function — нет?
Что такое замыкание?
Отвечено верно: 0 из 5

Функция как значение: обратные вызовы​

Функция в JavaScript — обычное значение. Её можно положить в переменную, в поле объекта, в массив, передать другой функции и вернуть из функции. Живой запуск:

const ops = { double: (x) => x * 2 };
console.log(typeof ops.double, ops.double(21)); // function 42

Функция, переданная другой функции, называется обратным вызовом (callback): «вот тебе кусок работы, вызови его сам, когда понадобится». Ты уже видел это в map, filter, forEach. Ещё один типичный пример — сортировка:

const txs = [{ id: 'a', amount: 5 }, { id: 'b', amount: 1 }, { id: 'c', amount: 9 }];
console.log(txs.sort((x, y) => x.amount - y.amount).map((t) => t.id).join('')); // bac

Метод sort не знает, как сравнивать транзакции, — и не должен. Он получает функцию сравнения и вызывает её столько раз, сколько нужно.

Самая частая ошибка при передаче обратного вызова — лишние скобки:

setTimeout(check, 1000); // правильно: передали функцию
setTimeout(check(), 1000); // неправильно: вызвали немедленно и передали её результат

Во втором случае check выполнится прямо сейчас, а через секунду setTimeout попытается вызвать то, что она вернула. Если это undefined — ничего не произойдёт. Молча.

Функция, возвращающая функцию​

Обратная ситуация: функция отдаёт наружу другую функцию, заранее «настроенную».

const withRate = (rate) => (size) => size * rate;
const feeMain = withRate(0.001);
console.log(feeMain(1000)); // 1
console.log(withRate(0.01)(1000)); // 10

Читается справа налево: withRate принимает ставку и возвращает функцию, которая принимает размер. Две пары скобок подряд в последней строке — это два вызова: сначала получили функцию, потом сразу её вызвали.

Приём выглядит вычурно, пока не столкнёшься с задачей «одна и та же операция, но с разной настройкой»: клиент для тестовой и основной сети, логгер с разным префиксом, проверка с разным порогом. Вместо того чтобы таскать настройку в каждый вызов, её фиксируют один раз.

Внутри здесь работает всё то же замыкание: возвращённая функция помнит rate.

Отвечено вопросов: 0 из 3
Что такое обратный вызов (callback)?
В чём разница между setTimeout(check, 1000) и setTimeout(check(), 1000)?
Что делает withRate(0.01)(1000) при const withRate = (rate) => (size) => size * rate?
Отвечено верно: 0 из 3

this и почему стрелки ведут себя иначе​

Единственное принципиальное отличие стрелочных функций от обычных — поведение this. Живой запуск в строгом режиме:

'use strict';
const node = {
name: 'узел-1',
regular() { return this.name; },
arrow: () => (typeof this === 'undefined' ? 'this отсутствует' : String(this && this.name)),
};

console.log(node.regular()); // узел-1
console.log(node.arrow()); // undefined

const f = node.regular;
f(); // падает
console.log(f.call(node)); // узел-1
TypeError: Cannot read properties of undefined (reading 'name')

Разбор трёх результатов:

  1. Обычный метод сработал. У обычной функции this определяется в момент вызова и указывает на то, что стоит слева от точки: вызвали node.regular() — this стал node.
  2. Стрелка не увидела объект. У стрелочной функции своего this нет вообще; она берёт его из места, где написана, — а написана она на верхнем уровне модуля, где никакого объекта нет.
  3. Метод, оторванный от объекта, упал. В f попала сама функция, слева от точки при вызове ничего нет, this в строгом режиме стал undefined, и обращение к его полю дало TypeError. Метод call позволяет указать this явно — так и получилось вернуть имя.

Отсюда рабочее правило: методы объектов и классов пиши обычными функциями, короткие вставки внутри них — стрелками. Именно потому, что стрелка не заводит свой this, обработчик внутри метода видит тот же this, что и сам метод, — а с обычной функцией пришлось бы городить const self = this или bind.

Интересный факт

TypeError: Cannot read properties of undefined (reading 'name') — самое частое сообщение в JavaScript вообще. В контексте функций оно почти всегда означает одно: метод оторвали от объекта. Классический случай — передали obj.method как обратный вызов вместо () => obj.method().

Чистые функции и побочные эффекты​

Функция называется чистой, если она зависит только от своих аргументов и ничего не меняет снаружи. Одинаковые аргументы — всегда одинаковый результат.

function fee(size) { return size * 0.001; } // чистая

Нечистая делает что-то ещё — меняет внешнюю переменную, пишет в файл, ходит в сеть. Живой запуск показывает, чем это неудобно:

let total = 0;
function impure(x) { total += x; return total; }
console.log(impure(5), impure(5)); // 5 10

Один и тот же вызов дал разные ответы. Такую функцию нельзя переставить местами с соседней, нельзя вызвать дважды «на всякий случай», нельзя проверить в отрыве от остальной программы, а ошибку в ней ищут по всему файлу.

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

Рекурсия и предел стека​

Функция может вызвать саму себя. Классический пример — факториал:

function fact(n) { return n <= 1 ? 1 : n * fact(n - 1); }
console.log(fact(5)); // 120

В рекурсии обязательны две части: условие остановки (n <= 1) и шаг, приближающий к остановке (n - 1). Забыть любую — получить бесконечную рекурсию, и она, в отличие от бесконечного цикла, падает довольно быстро:

RangeError: Maximum call stack size exceeded

Перевод: «превышен максимальный размер стека вызовов». У каждой незавершённой функции есть место в памяти — кадр стека; их количество ограничено примерно десятком тысяч. Это сообщение — надёжный признак рекурсии без условия остановки (или с условием, до которого не доходит).

Рекурсия естественна там, где данные вложены сами в себя: дерево блоков, разбор JSON, обход каталогов. Для простого перебора списка почти всегда проще и быстрее обычный цикл.

Отвечено вопросов: 0 из 3
Почему оторванный от объекта метод падает с TypeError: Cannot read properties of undefined?
Какая функция чистая?
Чего обязательно не хватает функции, падающей с RangeError: Maximum call stack size exceeded?
Отвечено верно: 0 из 3

Что добавляет TypeScript: сигнатура функции​

В TypeScript у функции появляется сигнатура — типы параметров и типа результата:

function fee(size: number, rate: number): number {
return size * rate;
}
Нажми на часть выражения
СИГНАТУРА ЧИТАЕТСЯ ПО ДВОЕТОЧИЯМfunction calcFee(sum: number, pct: number): numberтипы параметров — внутри скобоктип результата — после скобокво время проверкиcalcFee('1000', 3) — ошибка сразуcalcFee(1000) — ошибка сразупосле сборкиfunction calcFee(sum, pct)типы исчезли, тело осталось
Сигнатура читается по двоеточиям: внутри скобок — типы параметров, после скобок — тип результата. Тело функции при сборке остаётся, типы исчезают.

Что это даёт на практике — четыре живые ошибки компилятора:

error TS2554: Expected 2 arguments, but got 1.
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
error TS2322: Type 'number' is not assignable to type 'string'.
error TS7006: Parameter 'x' implicitly has an 'any' type.

Построчно: не хватило аргумента; передали строку вместо числа; вернули число там, где обещана строка; у параметра не указан тип и вывести его неоткуда. Последняя — про режим strict, и именно она заставляет писать типы параметров: там, где TypeScript не может вывести тип сам, он требует его указать.

Тип результата чаще всего писать не обязательно — компилятор выведет его из return. Но у функций, которые ты отдаёшь наружу, его пишут явно: тогда ошибка появится в самой функции, а не у того, кто ей воспользовался.

Особый тип результата — void: «функция ничего не возвращает». Попытка что-то вернуть даст:

error TS2322: Type 'number' is not assignable to type 'void'.

Тип бывает и у самой функции целиком:

const h: (a: number) => number = (a: string) => 1;
error TS2322: Type '(a: string) => number' is not assignable to type '(a: number) => number'.
Types of parameters 'a' and 'a' are incompatible.

Запись (a: number) => number — это тип функции: принимает число, возвращает число. Стрелка здесь не создаёт функцию, а описывает её форму. Такой тип пишут, когда функцию передают параметром: function retry(action: () => Promise<void>, times: number).

Необязательные параметры и проверка на пустоту​

Знак вопроса после имени параметра делает его необязательным:

function greet(name?: string) {
return `привет, ${name ?? 'аноним'}`;
}

Два правила, которые компилятор проверяет сам.

Первое: необязательные параметры идут последними.

error TS1016: A required parameter cannot follow an optional parameter.

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

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

function b(name?: string) { return name.length; }
error TS18048: 'name' is possibly 'undefined'.

Перевод: «name, возможно, undefined». Чинится проверкой, значением по умолчанию или оператором ??. Это ровно то сужение типа, о котором была речь в главе про условия: после if (!name) return 0; компилятор перестаёт ругаться, потому что дальше name точно строка.

Запись name?: string и name: string = 'аноним' решают похожие задачи, но по-разному: первая допускает undefined внутри, вторая подставляет значение и внутри функции всегда даёт строку. Если есть разумное значение по умолчанию — второй вариант удобнее.

Отвечено вопросов: 0 из 4
Что означает двоеточие после закрывающей скобки в function fee(size: number): number?
error TS7006: Parameter "x" implicitly has an "any" type. Что делать?
Почему function a(x?: number, y: number) не компилируется?
Что описывает запись (a: number) => number в позиции типа?
Отвечено верно: 0 из 4

Как называть функции и сколько делать параметров​

Правила короткие, но экономят часы.

Имя — глагол или ответ на вопрос. fetchBalance, signTx, isConfirmed, feeFor. Функция, возвращающая логическое значение, начинается с is, has или can — тогда if (isConfirmed(tx)) читается как предложение. Имена вроде data, process, handle не говорят ничего.

Одна функция — одно дело. Если в описании функции появляется слово «и» («считает комиссию и отправляет транзакцию»), это две функции. Проверяется просто: такую функцию невозможно назвать коротко.

Параметров — до трёх. Дальше передавай объект: вызов станет самодокументированным, а порядок перестанет иметь значение.

Логическое значение параметром — плохая идея. В вызове send(tx, true) смысл true не восстановить, не открыв объявление. Либо объект с именованным полем, либо две отдельные функции.

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

Типичные ошибки: текст, перевод, причина, починка​

СообщениеПереводЧто произошлоКак чинить
ReferenceError: Cannot access 'arrow' before initializationОбращение до инициализацииСтрелочная функция вызвана выше своего объявленияПеренести объявление выше вызова
TypeError: fee is not a functionfee не функцияИмя занято не функцией, либо опечатка в имени, либо забыт импортПроверить typeof fee и написание имени
TypeError: Cannot read properties of undefined (reading 'name')Чтение поля у undefinedМетод оторвали от объекта — this стал undefinedПередавать () => obj.method() вместо obj.method
RangeError: Maximum call stack size exceededПереполнение стека вызововРекурсия без условия остановкиДобавить условие выхода и шаг к нему
error TS2554: Expected 2 arguments, but got 1Ожидалось 2 аргументаПропущен аргумент при вызовеПередать все обязательные или сделать параметр необязательным
error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'Строку нельзя передать вместо числаПерепутан порядок аргументов или тип значенияПроверить порядок и типы аргументов
error TS7006: Parameter 'x' implicitly has an 'any' typeНеявный any у параметраУ параметра не указан тип, вывести неоткудаДописать тип параметра
error TS1016: A required parameter cannot follow an optional parameterОбязательный после необязательногоПорядок параметров нарушенПереставить необязательные в конец
error TS18048: 'name' is possibly 'undefined'Возможно undefinedНеобязательный параметр используют без проверкиПроверка, ?? или значение по умолчанию
error TS2322: Type 'number' is not assignable to type 'void'Числа нельзя вернуть из voidФункция объявлена как ничего не возвращающая, но возвращаетИсправить тип результата или убрать значение из return
Функция вернула undefined, ошибки нет—Забыт return, либо значение перенесено на строку ниже returnПроверить, что значение стоит на одной строке с return
Обратный вызов сработал сразу—При передаче написали check() вместо checkУбрать скобки или обернуть в стрелку

Тренажёр: функции вслепую​

Цель: скорость ≥ 110 зн/мин, точность ≥ 90%

function feeFor(size, rate = 0.001) { return size * rate; }
клавиша ``клавиша 11клавиша 22клавиша 33клавиша 44клавиша 55клавиша 66клавиша 77клавиша 88клавиша 99клавиша 00клавиша --клавиша ==клавиша ⌫⌫клавиша TabTabклавиша QQклавиша WWклавиша EEклавиша RRклавиша TTклавиша YYклавиша UUклавиша IIклавиша OOклавиша PPклавиша [[клавиша ]]клавиша \\клавиша Caps LockCaps Lockклавиша AAклавиша SSклавиша DDклавиша FFклавиша GGклавиша HHклавиша JJклавиша KKклавиша LLклавиша ;;клавиша ''клавиша EnterEnterклавиша ShiftShiftклавиша ZZклавиша XXклавиша CCклавиша VVклавиша BBклавиша NNклавиша MMклавиша ,,клавиша ..клавиша //клавиша ShiftShiftклавиша CtrlCtrlклавиша WinWinклавиша AltAltклавиша ПробелПробелклавиша AltAltклавиша WinWinклавиша MenuMenuклавиша CtrlCtrl
следующая: F — указательный левой руки

Символы этой главы​

ЗнакНазваниеЧто делает
functionключевое словообъявляет функцию; такое объявление поднимается наверх
( ) после именискобки вызоваименно они запускают функцию; без них имя — просто значение
=>стрелкакраткая форма записи функции; без фигурных скобок возвращает результат сама
=> ({ … })круглые скобки вокруг объектанужны, чтобы фигурная скобка не была понята как начало блока
... перед параметромостаточные параметрысобирают все лишние аргументы в массив
= в списке параметровзначение по умолчаниюподставляется, когда аргумент не передан
{ } в списке параметровдеструктуризацияразбирает пришедший объект на именованные поля
? после имени параметранеобязательный параметрзначение может отсутствовать; только в TypeScript
: внутри скобоктип параметратолько в TypeScript
: после скобоктип результататолько в TypeScript
(a: number) => numberтип функцииописывает форму функции, а не создаёт её
voidтип «ничего не возвращает»попытка вернуть значение даст ошибку

Что должен уметь​

  • Объявлять функцию и вызывать её, объясняя, что делают скобки.
  • Отличать параметр от аргумента и помнить, что сопоставление идёт по порядку.
  • Помнить, что чистый JavaScript не проверяет число аргументов, а TypeScript проверяет.
  • Пользоваться return и объяснять, почему код после него не выполняется.
  • Знать, что функция без return возвращает undefined, и узнавать эту ловушку по симптому.
  • Задавать значения по умолчанию, собирать остаток через ... и передавать объект вместо длинного списка параметров.
  • Записывать функцию тремя способами и объяснять разницу между телом-выражением и телом-блоком у стрелки.
  • Возвращать объект из стрелки, не забывая круглые скобки.
  • Объяснять подъём и узнавать Cannot access … before initialization.
  • Объяснять замыкание на примере счётчика.
  • Передавать функцию как значение и не ставить лишние скобки при передаче обратного вызова.
  • Писать функцию, возвращающую функцию, и понимать две пары скобок подряд.
  • Объяснять разницу this в обычной и стрелочной функции и узнавать оторванный от объекта метод.
  • Отличать чистую функцию от нечистой и отделять вычисление от действия.
  • Писать рекурсию с условием остановки и узнавать Maximum call stack size exceeded.
  • Читать и писать сигнатуру: типы параметров, тип результата, void, тип функции.
  • Пользоваться необязательными параметрами и понимать TS1016 и TS18048.
  • Называть функции глаголами и держать число параметров в пределах трёх.
Углубиться

bind, call, apply. Три метода, позволяющие задать this вручную. call(obj, a, b) вызывает функцию немедленно с указанным this, apply(obj, [a, b]) — то же самое, но аргументы массивом, bind(obj) ничего не вызывает, а возвращает новую функцию с намертво привязанным this. До появления стрелок bind стоял в каждом втором обработчике; сейчас его в основном узнают в чужом коде.

Стрелка не имеет не только this. У стрелочных функций нет ни arguments, ни prototype, их нельзя вызвать через new и нельзя использовать как генератор. Это не ограничения, а следствие: стрелка задумана как короткая вставка, а не как самостоятельный объект.

arguments. В обычных функциях доступен псевдомассив всех переданных аргументов — живой запуск даёт old(1, 2, 3).length === 3. Приём старый: в новом коде вместо него пишут остаточные параметры ...args, у которых настоящий массив и понятный тип.

Перегрузка в TypeScript. Одну функцию можно описать несколькими сигнатурами: сначала объявления без тела, затем одна реализация. Компилятор подберёт подходящую по типам аргументов. Приём нужен, когда функция ведёт себя по-разному в зависимости от типа входа, — например, принимает и строковый идентификатор, и готовый объект.

Стоимость вызова. Каждый вызов создаёт кадр стека; кадров конечное число, отсюда RangeError. Некоторые языки умеют хвостовую оптимизацию — если рекурсивный вызов стоит последним, кадр переиспользуется. В стандарте JavaScript такая оптимизация описана, но движки её в основном не реализуют, поэтому глубокую рекурсию в вебе переписывают циклом.

Функции высшего порядка. Общее название для функций, принимающих или возвращающих функции. map, filter, sort, setTimeout, withRate — все они из этой семьи. Термин встретится в любой статье про функциональный подход, и за ним не стоит ничего сложнее того, что уже разобрано в этой главе.

Дальше по теме:

Сводная проверка по всей главе: 15 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.

Куда дальше: проверенные ресурсы​

Видео по теме

ВидеоФункции в JavaScript на практике: виды функций, стрелочные функции, параметры, аргументы, returnPomazkov JS
ВидеоСтрелочные функции JavaScriptWebDev с нуля. Канал Алекса Лущенко
ВидеоЗамыкания, просто и с примерами. Функции JavaScriptWebDev с нуля. Канал Алекса Лущенко
ВидеоJavaScript. Урок 9. Стрелочные функцииLoftschool
ВидеоФункции, стрелочные функции, контексты, замыкания в JavaScriptTimur Shemsedinov

Комментарии

Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.