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