Функции в JavaScript. Возврат значения. Параметры.
Один из способ объявления функции выглядит так.
function add(x, y) {
return x + y;
}
Мы объявляем функцию add
с двумя параметрами — x
и y
. Функция возвращает сумму этих параметров.
Вызов функции выполняется следующим образом.
add(2, 3); // 5
Если явно не указывать возвращаемое значение, как в этом примере, функция вернёт undefined
.
function lazy() {
}
lazy(); // undefined
Функции можно вызывать с любым количеством аргументов.
Если параметр не передан при вызове, ему будет присвоено значение undefined
.
function asIs(x) {
return y;
}
asIs(y); // undefined
То же самое справедливо для функции с несколькими аргументами.
function myMin(a, b) {
return a < b ? a : b;
}
myMin(2, 7); // 2
myMin(13, 7); // 7
myMin(13); // undefined
Мы можем проверить, передан ли параметр, и если нет, задать ему значение по умолчанию.
function myMin(a, b) {
if (b === undefined) {
return a;
}
return a < b ? a : b;
}
myMin(13); // 13
При объявлении функции необязательные аргументы, как правило, располагают в конце списка.
Зачастую для задания значения по умолчанию используют более короткую форму с импользованием оператора ||
.
function myMin(a, b) {
b = b || Infinity;
return a < b ? a : b;
}
myMin(2, 7); // 2
myMin(13, 7); // 7
myMin(-13); // -13
Этот способ следует использовать с осторожностью. Если параметр может принимать значения, приводимые к false
— например, 0
, ''
или null
, они будут перезаписаны значением по умолчанию.
function getSalary(rate, days) {
days = days || 22;
return rate * days;
}
getSalary(3, 10); // 30
getSalary(1); // 22
getSalary(2, 0); // 44 ???
В некоторых языках (например, в python), при вызове функции можно передавать именованные аргументы.
add(x=2, y=3);
Это удобно по нескольким причинам. Во-первых, в момент вызова мы видим, для чего используется тот или иной аргумент. Во-вторых, становится неважным порядок передачи аргументов. Это особенно удобно, когда у функции есть несколько необязательных аргументов.
В JavaScript мы можем имитировать именованные аргументы с помощью литерала объекта.
BMI({ m: 60, h: 1.7 }) // 20.7
Обратите внимание на скобки — на самом деле мы передаём в функцию один аргумент — объект, свойствами которого являются наши как-бы именованные аргументы.
Объявление функции будет выглядеть слудующим образом.
function BMI(params) {
var h = params.h;
return params.m / (h * h);
}
Однако, у такого подхода есть несколько недостатков:
- аргументы нужно вызывать через точку (как свойства объекта)
- по сигнатуре функции нельзя понять, с какими параметрами она вызывается
Поэтому такой подход не следует использовать повсеместно. На практике его обычно применяют, когда функция принимает большое количество необязательных конфигурационных параметров.
Мы также можем передать больше аргументов, чем определено в функции. Избыточные аргументы будут проигнорированы.
function myMin(a, b) {
return a < b ? a : b;
}
myMin(7, 5, 1); // 5
Внутри функции доступна специальная переменная arguments
, которая позволяет получить доступ ко всем переданным аргументам.
function myMin(a, b) {
var min = a < b ? a : b;
var c = arguments[2];
return c < min ? c : min;
}
myMin(2, 3, 4); // 2
myMin(20, 3, 4); // 3
myMin(20, 30, 4); // 4
С его помощью мы можем объявить функцию, принимающую любое количество аргументов.
Например, эта функция суммирует все переданные при вызове числа.
function addMany()
{
var sum = 0;
var len = arguments.length;
for (var i = 0; i < len; i += 1) {
sum += arguments[i];
}
return sum;
}
addMany(2, 3, 4); // 9
По историческим причинам arguments
— не массив, а так называемый array-like object. Поэтому у него нет методов массива, таких как forEach
или map
.
К счастью, есть способ преобразовать его к массиву с помощью следующей конструкции.
var args = [].slice.call(arguments);
Тут нам играет на руку динамическая природа языка JavaSctipt. Нам понадобится метод slice
, который может использоваться для копирования массива. Мы берём этот метод у только что созданного массива, и вызываем его на нашем объекте.
Эта конструкция аналогична следующей:
// не надо использовать этот код
// он здесь только для наглядного описания
arguments.slice = [].slice;
var args = arguments.slice();
В следующих лекциях мы более подробно поговорим о методе call
.
Рассмотрим следующий пример. Нам нужно найти минимальное значение из элементов, которые лежат в массиве numbers
.
Math.min(3, 5, 2, 6); // 2
var numbers = [3, 5, 2, 6];
Math.min(numbers); // NaN
У нас есть функция Math.min
, но она принимает несколько аргументов. Если просто передать массив в функцию — результат будет отличаться от наших ожиданий.
На помощь нам приходит метод apply
.
Первым аргументом метод принимает так называемый контекст — объект, на который будет указывать ключевое слово this
внутри функции. О контексте и ключевом слове this
мы подробнее поговорим в следующих лекциях, а пока просто передадим первым аргументом null
.
В качестве второго аргумента метод принимает массив, элементы которого будут переданы в функцию.
var numbers = [3, 5, 2, 6];
Math.min.apply(null, numbers); // 2
Другой полезный метод — bind
, позволяет выполнить так называемое частичное применение функции.
Частичное применение — это процесс предачи части аргументов функции, который возвращает другую функцию, принимающую оставшиеся аргументы (функцию меньшей арности).
Рассмотрим функцию возведения в степень — Math.pow
. Она принимает два аргумента.
Math.pow(2, 4); // 16
Math.pow(2, 10); // 1024
C помощью метода bind
мы можем передать только первый аргумент и получить функцию, возводящую в степень определённой число — в нашем случае, двойку.
var binPow = Math.pow.bind(null, 2);
binPow(4); // 16
binPow(10); // 1024
Функции в JavaScript являются объектами первого класса. Это значит, что функции являются таким же полноценным типом данных, как число, строка или объект.
Функцию можно положить в переменную:
function add(a, b) {
return a + b;
}
var sum = add;
sum(1, 3); // 4
Функуию можно передать в качестве аргумента другой функции:
function multiplyBy2(x) {
return x * 2;
}
var numbers = [3, 5, 9];
numbers.map(multiplyBy2); // [6, 10, 18]
Наконец, функция может быть возвращена результате выполнения другой функции:
function getAdd() {
function add(a, b) {
return a + b;
}
return add;
}
var myAdd = getAdd();
myAdd(1, 3); // 4
В начале лекции мы говорили об одном способе объявления функции. Он называется function declaration.
function add(x, y) {
return x + y;
}
Функция, определённая таким образом, создаётся на этапе интерпретации программы, то есть ещё до начала выполнения кода.
Это приводит к эффекту, называемому hoisting, то есть всплытие или подём. Проявляется он в том, что функцию можно вызывать до её объявления.
add(2, 3); // 5
function add(x, y) {
return x + y;
}
Это происходит потому, что интерпретатор поднимает объявление функции над выполняемым кодом, и исходный код приобретает следующий вид:
function add(x, y) {
return x + y;
}
add(2, 3);
Второй способ объявления функции называется function expression и выглядит так:
var add = function (x, y) {
return x + y;
};
В этом примере мы создаём функцию без имени — так называемую "анонимную функцию" и кладём её в переменную add
.
Несмотря на общую схожесть с предыдущим вариантом, есть одно существенное различие — эта функция создаётся в момент выполнения кода, поэтому её нельзя использовать до определения.
add(2, 3); // TypeError: add is not a function
var add = function (x, y) {
return x + y;
};
Функция, объявленная с помощью function expression, не обязательно должна быть анонимной. Мы можем указать для неё идентификатор.
var add = function hidden() {
return typeof hidden;
}
add(); // function
В отличие от function declaration, такой идентификатор будет доступен только внутри функции, но не виден снаружи.
var add = function hidden() {
return typeof hidden;
}
hidden(); // ReferenceError: hidden is not defined
Это может быть полезно при определении рекурсивных функций. Такие функции могут вызывать сами себя.
Для примера рассмотрим функцию вычисления факториала.
var fac = function me(n) {
if (n > 0) {
return n * me(n-1);
} else {
return 1;
}
};
console.log(fac(3)); // 6
Мы рассмотрели два варианта объявления функции. Какой из них лучше?
function add(x, y) {
return x + y;
}
var add = function (x, y) {
return x + y;
};
Оба варианта полностью допустимы. В общем случае можно использовать первый спобоб — function declaration. Этот способ обладает более простым синтаксисом, а также позволяет использовать эффект всплытия.
Есть и ещё один способ объявить функцию, но он встречается достаточно редко.
> var add = new Function('x', 'y', 'return x + y');
> add(2, 3)
5
Мы можем использовать конструктор Function
. Этот способ может пригодиться, если требуется создать тело функции на этапе выполнения кода.
Область видимости переменной — это часть кода, в пределах которой эта переменная доступна.
В Javascript область видимости переменной ограничивается функцией.
function greet() {
var text = 'Привет';
console.log(text);
}
greet(); // 'Привет'
console.log(text); // Uncaught ReferenceError: text is not defined
Определяя функцию внутри другой функции, мы создаём новую область видимости. Переменная из внешней области видимости доступна во вложенной
function greet() {
var text = 'Привет';
function nested() {
console.log(text);
}
nested();
}
greet(); // 'Привет'
Если во внутренней области видимости объявить переменную с тем же именем, что и во внешней, она перекроет переменную из внешней области видимости. Этот эффект называется shadowing (затенение).
function greet() {
var text = 'Привет';
function nested() {
var text = 'Добрый день';
console.log(text);
}
nested();
}
greet(); // 'Добрый день'
В отличие от других языков, блок не создаёт область видимости:
function greet() {
if (true) {
var text = 'Привет';
}
console.log(text); // 'Привет'
}
В этом примере срабатывает механизм всплытия, о котором мы говорили ранее.
Интерпретатор поднимает объявление переменной к началу области видимости, то есть к началу функции:
function greet() {
var text;
if (true) {
text = 'Привет';
}
console.log(text); // 'Привет'
}
Иногда возникает необходимость искуственно создать область видимости. Например, если мы хотим ограничить доступ к некторым переменным.
Для этого можно использовать способ, называемый "немедленно вызываемой функцией" или IIFE (immediately-invoked function expression).
function foo() {
(function () {
var bar = 4;
}());
console.log(bar); // Reference Error
}
Мы создаём функцию, чтобы ввести новую область видимости. И сразу же после создания вызываем её.
Скобки фокруг функции нужны для того, чтобы интерпретатор понял, что мы хотим использовать function expression.
function () {
}()
// SyntaxError: Unexpected token (
Если попробовать немедленно вызвать функцию, объявленную с помощью function declaration, мы получим ошибку синтаксиса — язык не разрешает такую конструкцию.
Чтобы исправить ситуацию, нам нужно поместить любой символ перед ключевым словом function
. Сделать это мы можем любым способом:
!function () {
}();
void function () {
}();
Рассмотрим возможности, которы вносит новый стандарт языка ECMAScript 2015.
Для создания области видимости нужно было использовать IIFE.
function foo() {
(function () {
var bar = 4;
}());
}
Теперь можно объявить переменную с блочной областью видимости с помощью ключевого слова let
.
function foo() {
if (true) {
let bar = 4;
}
}
Для того, чтобы установить значение параметра по умолчанию, нужно было проверять его значение.
function add(x, y) {
if (y === undefined) {
y = 0;
}
return x + y;
}
Теперь можно задать значение по умолчанию в сигнатуре функции.
function add(x, y = 0) {
return x + y;
}
Для доступа к произвольному числу аргументов нужно было обращаться к переменной arguments
.
function add() {
var sum = 0;
for (var i, l = arguments.length; i < l; i += 1) {
sum += arguments[i];
}
return sum;
}
Теперь можно использовать оператор ...
чтобы получить массив переданных аргументов.
function add(...args) {
var sum = 0;
args.forEach(function (arg) {
sum += arg;
});
return sum;
}
Одним из недостатков именованных аргументов являлась необходимость обращаться к параметрам через точку.
function add(options) {
return options.x + options.y;
}
add({ x: 1, y: 2});
Теперь мы можем извлечь значения из переданного объекта прямо в сигнатуре функции:
function add({ x, y }) {
return x + y;
}
add({ x: 1, y: 2});