Функции
В функциях основные изменения касаются передачи параметров, плюс введена дополнительная короткая запись через стрелочку =>
.
Параметры по умолчанию
Можно указывать параметры по умолчанию через равенство =
, например:
function showMenu(title = "Без заголовка", width = 100, height = 200) {
alert(`${title} ${width} ${height}`);
}
showMenu("Меню"); // Меню 100 200
Параметр по умолчанию используется при отсутствующем аргументе или равном undefined
, например:
function showMenu(title = "Заголовок", width = 100, height = 200) {
alert(`title=${title} width=${width} height=${height}`);
}
// По умолчанию будут взяты 1 и 3 параметры
// title=Заголовок width=null height=200
showMenu(undefined, null);
При передаче любого значения, кроме undefined
, включая пустую строку, ноль или null
, параметр считается переданным, и значение по умолчанию не используется.
Параметры по умолчанию могут быть не только значениями, но и выражениями.
Например:
function sayHi(who = getCurrentUser().toUpperCase()) {
alert(`Привет, ${who}!`);
}
function getCurrentUser() {
return 'Вася';
}
sayHi(); // Привет, ВАСЯ!
Заметим, что значение выражения getCurrentUser().toUpperCase()
будет вычислено, и соответствующие функции вызваны -- лишь в том случае, если это необходимо, то есть когда функция вызвана без параметра.
В частности, выражение по умолчанию не вычисляется при объявлении функции. В примере выше функция getCurrentUser()
будет вызвана именно в последней строке, так как не передан параметр.
Оператор spread вместо arguments
Чтобы получить массив аргументов, можно использовать оператор …
, например:
function showName(firstName, lastName, *!*...rest*/!*) {
alert(`${firstName} ${lastName} - ${rest}`);
}
// выведет: Юлий Цезарь - Император,Рима
showName("Юлий", "Цезарь", "Император", "Рима");
В rest
попадёт массив всех аргументов, начиная со второго.
Заметим, что rest
-- настоящий массив, с методами map
, forEach
и другими, в отличие от arguments
.
```"Оператор … должен быть в конце"
Оператор
…` собирает "все оставшиеся" аргументы, поэтому такое объявление не имеет смысла:
function f(arg1, ...rest, arg2) { // arg2 после ...rest ?!
// будет ошибка
}
Параметр ...rest
должен быть в конце функции.
Выше мы увидели использование `...` для чтения параметров в объявлении функции. Но этот же оператор можно использовать и при вызове функции, для передачи массива параметров как списка, например:
```js
'use strict';
let numbers = [2, 3, 15];
// Оператор ... в вызове передаст массив как список аргументов
// Этот вызов аналогичен Math.max(2, 3, 15)
let max = Math.max(*!*...numbers*/!*);
alert( max ); // 15
Формально говоря, эти два вызова делают одно и то же:
Math.max(...numbers);
Math.max.apply(Math, numbers);
Похоже, что первый -- короче и красивее.
Деструктуризация в параметрах
Если функция получает объект, то она может его тут же разбить в переменные:
'use strict';
let options = {
title: "Меню",
width: 100,
height: 200
};
function showMenu({title, width, height}) {
alert(`${title} ${width} ${height}`); // Меню 100 200
}
showMenu(options);
Можно использовать и более сложную деструктуризацию, с соответствиями и значениями по умолчанию:
'use strict';
let options = {
title: "Меню"
};
function showMenu({title="Заголовок", width:w=100, height:h=200}) {
alert(`${title} ${w} ${h}`);
}
// объект options будет разбит на переменные
showMenu(options); // Меню 100 200
Заметим, что в примере выше какой-то аргумент у showMenu()
обязательно должен быть, чтобы разбить его на переменные.
Если хочется, чтобы функция могла быть вызвана вообще без аргументов -- нужно добавить ей параметр по умолчанию -- уже не внутрь деструктуризации, а в самом списке аргументов:
'use strict';
function showMenu({title="Заголовок", width:w=100, height:h=200} *!*= {}*/!*) {
alert(`${title} ${w} ${h}`);
}
showMenu(); // Заголовок 100 200
В коде выше весь объект аргументов по умолчанию равен пустому объекту {}
, поэтому всегда есть что деструктуризовать.
Имя "name"
В свойстве name
у функции находится её имя.
Например:
'use strict';
function f() {} // f.name == "f"
let g = function g() {}; // g.name == "g"
alert(`${f.name} ${g.name}`) // f g
В примере выше показаны Function Declaration и Named Function Expression. В синтаксисе выше довольно очевидно, что у этих функций есть имя name
. В конце концов, оно указано в объявлении.
Но современный JavaScript идёт дальше, он старается даже анонимным функциям дать разумные имена.
Например, при создании анонимной функции с одновременной записью в переменную или свойство -- её имя равно названию переменной (или свойства).
Например:
'use strict';
// свойство g.name = "g"
let g = function() {};
let user = {
// свойство user.sayHi.name == "sayHi"
sayHi: function() { };
}
Функции в блоке
Объявление функции Function Declaration, сделанное в блоке, видно только в этом блоке.
Например:
'use strict';
if (true) {
sayHi(); // работает
function sayHi() {
alert("Привет!");
}
}
sayHi(); // ошибка, функции не существует
То есть, иными словами, такое объявление -- ведёт себя в точности как если бы let sayHi = function() {…}
было сделано в начале блока.
Функции через =>
Появился новый синтаксис для задания функций через "стрелку" =>
.
Его простейший вариант выглядит так:
'use strict';
let inc = x => x+1;
alert( inc(1) ); // 2
Эти две записи -- примерно аналогичны:
let inc = x => x+1;
let inc = function(x) { return x + 1; };
Как видно, "x => x+1"
-- это уже готовая функция. Слева от =>
находится аргумент, а справа -- выражение, которое нужно вернуть.
Если аргументов несколько, то нужно обернуть их в скобки, вот так:
'use strict';
let sum = (a,b) => a + b;
// аналог с function
// let inc = function(a, b) { return a + b; };
alert( sum(1, 2) ); // 3
Если нужно задать функцию без аргументов, то также используются скобки, в этом случае -- пустые:
'use strict';
// вызов getTime() будет возвращать текущее время
let getTime = () => `${new Date().getHours()} : ${new Date().getMinutes()}`;
alert( getTime() ); // текущее время
Когда тело функции достаточно большое, то можно его обернуть в фигурные скобки {…}
:
'use strict';
let getTime = () => {
let date = new Date();
let hours = date.getHours();
let minutes = date.getMinutes();
return `${hours}:${minutes}`;
};
alert( getTime() ); // текущее время
Заметим, что как только тело функции оборачивается в {…}
, то её результат уже не возвращается автоматически. Такая функция должна делать явный return
, как в примере выше, если конечно хочет что-либо возвратить.
Функции-стрелки очень удобны в качестве коллбеков, например:
`use strict`;
let arr = [5, 8, 3];
let sorted = arr.sort( (a,b) => a - b );
alert(sorted); // 3, 5, 8
Такая запись -- коротка и понятна. Далее мы познакомимся с дополнительными преимуществами использования функций-стрелок для этой цели.
Функции-стрелки не имеют своего this
Внутри функций-стрелок -- тот же this
, что и снаружи.
Это очень удобно в обработчиках событий и коллбэках, например:
'use strict';
let group = {
title: "Наш курс",
students: ["Вася", "Петя", "Даша"],
showList: function() {
this.students.forEach(
(student) => alert(`${this.title}: ${student}`)
)
}
}
group.showList();
// Наш курс: Вася
// Наш курс: Петя
// Наш курс: Даша
Здесь в forEach
была использована функция-стрелка, поэтому this.title
в коллбэке -- тот же, что и во внешней функции showList
. То есть, в данном случае -- group.title
.
Если бы в forEach
вместо функции-стрелки была обычная функция, то была бы ошибка:
'use strict';
let group = {
title: "Наш курс",
students: ["Вася", "Петя", "Даша"],
showList: function() {
this.students.forEach(function(student) {
alert(`${this.title}: ${student}`); // будет ошибка
})
}
}
group.showList();
При запуске будет "попытка прочитать свойство title
у undefined
", так как .forEach(f)
при запуске f
не ставит this
. То есть, this
внутри forEach
будет undefined
.
"Функции стрелки нельзя запускать с new
"
Отсутствие у функции-стрелки "своего this
" влечёт за собой естественное ограничение: такие функции нельзя использовать в качестве конструктора, то есть нельзя вызывать через new
.
"=>
это не то же самое, что .bind(this)
"
Есть тонкое различие между функцией стрелкой =>
и обычной функцией, у которй вызван .bind(this)
:
- Вызовом
.bind(this)
мы передаём текущийthis
, привязывая его к функции. - При
=>
привязки не происходит, так как функция стрелка вообще не имеет контекстаthis
. Поискthis
в ней осуществляется так же, как и поиск обычной переменной, то есть, выше в замыкании. До появления стандарта ES-2015 такое было невозможно. ```
Функции-стрелки не имеют своего arguments
В качестве arguments
используются аргументы внешней "обычной" функции.
Например:
'use strict';
function f() {
let showArg = () => alert(arguments[0]);
showArg();
}
f(1); // 1
Вызов showArg()
выведет 1
, получив его из аргументов функции f
. Функция-стрелка здесь вызвана без параметров, но это не важно: arguments
всегда берутся из внешней "обычной" функции.
Сохранение внешнего this
и arguments
удобно использовать для форвардинга вызовов и создания декораторов.
Например, декоратор defer(f, ms)
ниже получает функцию f
и возвращает обёртку вокруг неё, откладывающую вызов на ms
миллисекунд:
'use strict';
function defer(f, ms) {
return function() {
setTimeout(() => f.apply(this, arguments), ms)
}
}
function sayHi(who) {
alert(`Привет, ${who}!`);
}
let sayHiDeferred = defer(sayHi, 2000);
sayHiDeferred("Вася"); // Привет, Вася! через 2 секунды
Аналогичная реализация без функции-стрелки выглядела бы так:
function defer(f, ms) {
return function() {
let args = arguments;
let ctx = this;
setTimeout(function() {
return f.apply(ctx, args);
}, ms);
}
}
В этом коде пришлось создавать дополнительные переменные args
и ctx
для передачи внешних аргументов и контекста через замыкание.
Итого
Основные улучшения в функциях:
- Можно задавать параметры по умолчанию, а также использовать деструктуризацию для чтения приходящего объекта.
- Оператор spread (троеточие) в объявлении позволяет функции получать оставшиеся аргументы в массив:
function f(arg1, arg2, ...rest)
. - Тот же оператор spread в вызове функции позволяет передать её массив как список аргументов (вместо
apply
). - У функции есть свойство
name
, оно содержит имя, указанное при объявлении функции, либо, если его нет, то имя свойства или переменную, в которую она записана. Есть и некоторые другие ситуации, в которых интерпретатор подставляет "самое подходящее" имя. - Объявление Function Declaration в блоке
{...}
видно только в этом блоке. - Появились функции-стрелки:
- Без фигурных скобок возвращают выражение
expr
:(args) => expr
. - С фигурными скобками требуют явного
return
. - Не имеют своих
this
иarguments
, при обращении получают их из окружающего контекста. - Не могут быть использованы как конструкторы, с
new
.
- Без фигурных скобок возвращают выражение