Мы хотим сделать этот проект с открытым исходным кодом доступным для людей во всем мире. Пожалуйста, помогите нам перевести это руководство на другие языки.
КупитьEPUB/PDF
Поделиться
1 августа 2019 г.

Функции

Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

В функциях основные изменения касаются передачи параметров, плюс введена дополнительная короткая запись через стрелочку =>.

Параметры по умолчанию

Можно указывать параметры по умолчанию через равенство =, например:

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.
Поделиться

Комментарии

перед тем как писать...
  • Если вам кажется, что в статье что-то не так - вместо комментария напишите на GitHub.
  • Для одной строки кода используйте тег <code>, для нескольких строк кода — тег <pre>, если больше 10 строк — ссылку на песочницу (plnkr, JSBin, codepen...)
  • Если что-то непонятно в статье — пишите, что именно и с какого места.

AltStyle によって変換されたページ (->オリジナル) /