В функциях основные изменения касаются передачи параметров, плюс введена дополнительная короткая запись через стрелочку =>
.
Параметры по умолчанию
Можно указывать параметры по умолчанию через равенство =
, например:
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
должен быть в конце функции.
Выше мы увидели использование ...
для чтения параметров в объявлении функции. Но этот же оператор можно использовать и при вызове функции, для передачи массива параметров как списка, например:
'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 sum = 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
.
- Без фигурных скобок возвращают выражение