en.javascript.info/1-js/2-first-steps/20-function-parameters/article.md
Ilya Kantor 3dc3018fd1 works
2016-07-02 18:11:36 +03:00

18 KiB
Raw Blame History

Function parameters

The syntax of function parameters is very versatile.

It allows:

  • To specify values if the parameter if missing.
  • To gather parameters into an array and deal with it instead of variables.
  • To destructurize the object into parameters.
  • And more.

All these features aim to help us in writing good-looking and concise code.

Default values

A function can be called with any number of arguments. If a parameter is not provided, but listed in the declaration, then its value becomes undefined.

For instance, the aforementioned function showMessage(from, text) can be called with a single argument:

showMessage("Ann");

That's not an error. Such call would output "Ann: undefined", because text === undefined.

If we want to track when the function is called with a single argument and use a "default" value in this case, then we can check if text is defined, like here:

function showMessage(from, text) {
*!*
  if (text === undefined) {
    text = 'no text given';
  }
*/!*

  alert( from + ": " + text );
}

showMessage("Ann", "Hello!"); // Ann: Hello!
*!*
showMessage("Ann"); // Ann: no text given
*/!*

There are also other ways to supply "default values" for missing arguments:

  • Use operator ||:

    function showMessage(from, text) {
      text = text || 'no text given';
      ...
    }
    

    This way is shorter, but the argument is considered missing even if it exists, but is falsy, like an empty line, 0 or null.

  • Specify the default value after =:

    function showMessage(from, *!*text = 'no text given'*/!*) {
      alert( from + ": " + text );
    }
    
    showMessage("Ann"); // Ann: no text given
    

    Here 'no text given' is a string, but it can be a more complex expression, which is only evaluated and assigned if the parameter is missing. So, this is also possible:

    function showMessage(from, text = anotherFunction()) {
      // anotherFunction() is executed if no text given
    }
    

Arbitrary number of arguments

To support any number of arguments, we can use the rest operator ..., similar to destructuring:

function sumAll(...args) {
  let sum = 0;

  for(let arg of args) sum += arg;

  return sum;
}

alert( sumAll(1) ); // 1
alert( sumAll(1, 2) ); // 3
alert( sumAll(1, 2, 3) ); // 6

We also can put few first arguments into variables and gather only the rest:

function showName(firstName, lastName, ...rest) {
  alert( firstName + ' ' + lastName ); // Julius Caesar

  // the rest = ["Consul", "of the Roman Republic"]
  alert( rest[0] ); // Consul
  alert( rest[1] ); // of the Roman Republic
}

showName("Julius", "Caesar", "Consul", "of the Roman Republic");
The rest operator `…` gathers all remaining arguments, so the following has no sense:

```js
function f(arg1, ...rest, arg2) { // arg2 after ...rest ?!
  // error
}
```

The `...rest` must always be the last.

````smart header="The arguments variable"

In old times, there were no rest operator. But there was a special variable named arguments that contained all arguments by their index. It is still supported and can be used like this:

function showName() {
  alert( arguments[0] ); 
  alert( arguments[1] ); 
  alert( arguments.length ); 
}

// shows: Julius, Caesar, 2
showName("Julius", "Caesar");

// shows: Ilya, undefined, 1
showName("Ilya"); 

The downside is that arguments looks like an array, but it's not. It does not support many useful array features. It only exists for backwards compatibility. The rest operator is better.


## Destructuring in parameters

There are times when a function may have many parameters. Imagine a function that creates a menu. It may have a width, a height, a title, items list and so on.

Here's a bad way to write such function:

```js
function showMenu(title = "Untitled", width = 200, height = 100, items = []) {
  // ...
}
```

The real-life problem is how to remember the order of arguments. Usually IDEs try to help us, especially if the code is well-documented, but still... Another problem is how to call a function when most parameters are ok by default. 

Like this?

```js
showMenu("My Menu", undefined, undefined, ["Item1", "Item2"])
```

That's ugly. And becomes unreadable if we have not 4 but 10 parameters.

Destructuring comes to the rescue!

We can pass parameters as an object, and the function immediately destructurizes them into variables:

```js run
let options = {
  title: "My menu",
  items: ["Item1", "Item2"]
};

*!*
function showMenu({title = "Untitled", width = 200, height = 100, items = []}) {
*/!*
  alert( title + ' ' + width + ' ' + height ); // My Menu 100 200
  alert( items ); // Item1, Item2
}

showMenu(options);
```

We can also use the more complex destructuring with nestings and colon mappings:

```js run
let options = {
  title: "My menu",
  items: ["Item1", "Item2"]
};

*!*
function showMenu({
  title = "Untitled", 
  width:w = 100,  // width goes to w
  height:h = 200, // height goes to h
  items: [item1, item2] // items first element goes to item1, second to item2
}) {
*/!*
  alert( title + ' ' + w + ' ' + h ); // My Menu 100 200
  alert( item1 ); // Item1
  alert( item2 ); // Item2
}

showMenu(options); 
```

The syntax is the same as for a destructuring assignment:
```js
function({
  incoming property: parameterName = defaultValue
  ...
})
```

Please note that such destructuring assumes that `showMenu()` does have an argument. If we want all values by default, then we should specify an empty object:

```js
showMenu({});

// that would give an error
showMenu();
```

We can fix this of course, by making an empty object a value by default for the whole destructuring thing:


```js run
// simplified parameters a bit for clarity
function showMenu(*!*{ title="Menu", width=100, height=200 } = {}*/!*) {
  alert( title + ' ' + width + ' ' + height ); 
}

showMenu(); // Menu 100 200
```

In the code above, the whole arguments object is `{}` by default, so there's always something to destructurize.

## The spread operator

// TODO!!!

Выше мы увидели использование `...` для чтения параметров в объявлении функции. Но этот же оператор можно использовать и при вызове функции, для передачи массива параметров как списка, например:

```js run
'use strict';

let numbers = [2, 3, 15];

// Оператор ... в вызове передаст массив как список аргументов
// Этот вызов аналогичен Math.max(2, 3, 15)
let max = Math.max(*!*...numbers*/!*);

alert( max ); // 15
```

Формально говоря, эти два вызова делают одно и то же:

```js
Math.max(...numbers);
Math.max.apply(Math, numbers);
```

Похоже, что первый -- короче и красивее.

## Деструктуризация в параметрах


## Имя "name"

В свойстве `name` у функции находится её имя.

Например:

```js run
'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 идёт дальше, он старается даже анонимным функциям дать разумные имена.

Например, при создании анонимной функции с одновременной записью в переменную или свойство -- её имя равно названию переменной (или свойства).

Например:

```js
'use strict';

// свойство g.name = "g"
let g = function() {};

let user = {
  // свойство user.sayHi.name == "sayHi"
  sayHi: function() {}
};
```

## Функции в блоке

Объявление функции Function Declaration, сделанное в блоке, видно только в этом блоке.

Например:

```js run
'use strict';

if (true) {

  sayHi(); // работает

  function sayHi() {
    alert("Привет!");
  }

}
sayHi(); // ошибка, функции не существует
```

То есть, иными словами, такое объявление -- ведёт себя в точности как если бы `let sayHi = function() {…}` было сделано в начале блока.

## Функции через =>

Появился новый синтаксис для задания функций через "стрелку" `=>`.

Его простейший вариант выглядит так:
```js run
'use strict';

*!*
let inc = x => x+1;
*/!*

alert( inc(1) ); // 2
```

Эти две записи -- примерно аналогичны:

```js
let inc = x => x+1;

let inc = function(x) { return x + 1; };
```

Как видно, `"x => x+1"` -- это уже готовая функция. Слева от `=>` находится аргумент, а справа -- выражение, которое нужно вернуть.

Если аргументов несколько, то нужно обернуть их в скобки, вот так:

```js run
'use strict';

*!*
let sum = (a,b) => a + b;
*/!*

// аналог с function
// let inc = function(a, b) { return a + b; };

alert( sum(1, 2) ); // 3
```

Если нужно задать функцию без аргументов, то также используются скобки, в этом случае -- пустые:

```js run
'use strict';

*!*
// вызов getTime() будет возвращать текущее время
let getTime = () => new Date().getHours() + ':' + new Date().getMinutes();
*/!*

alert( getTime() ); // текущее время
```

Когда тело функции достаточно большое, то можно его обернуть в фигурные скобки `{…}`:

```js run
'use strict';

*!*
let getTime = () => {
  let date = new Date();
  let hours = date.getHours();
  let minutes = date.getMinutes();
  return hourse + ':' + minutes;
};
*/!*

alert( getTime() ); // текущее время
```

Заметим, что как только тело функции оборачивается в `{…}`, то её результат уже не возвращается автоматически. Такая функция должна делать явный `return`, как в примере выше, если конечно хочет что-либо возвратить.

Функции-стрелки очень удобны в качестве коллбеков, например:

```js run
`use strict`;

let arr = [5, 8, 3];

*!*
let sorted = arr.sort( (a,b) => a - b );
*/!*

alert(sorted); // 3, 5, 8
```

Такая запись -- коротка и понятна. Далее мы познакомимся с дополнительными преимуществами использования функций-стрелок для этой цели.

## Функции-стрелки не имеют своего this

Внутри функций-стрелок -- тот же `this`, что и снаружи.

Это очень удобно в обработчиках событий и коллбэках, например:

```js run
'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` вместо функции-стрелки была обычная функция, то была бы ошибка:

```js run
'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`.

```warn header="Функции стрелки нельзя запускать с `new`"
Отсутствие у функции-стрелки "своего `this`" влечёт за собой естественное ограничение: такие функции нельзя использовать в качестве конструктора, то есть нельзя вызывать через `new`.
```

```smart header="=> это не то же самое, что `.bind(this)`"
Есть тонкое различие между функцией стрелкой `=>` и обычной функцией, у которой вызван `.bind(this)`:

- Вызовом `.bind(this)` мы передаём текущий `this`, привязывая его к функции.
- При `=>` привязки не происходит, так как функция стрелка вообще не имеет контекста `this`. Поиск `this` в ней осуществляется так же, как и поиск обычной переменной, то есть, выше в замыкании. До появления стандарта ES-2015 такое было невозможно.
```

## Функции-стрелки не имеют своего arguments

В качестве `arguments` используются аргументы внешней "обычной" функции.

Например:

```js run
'use strict';

function f() {
  let showArg = () => alert(arguments[0]);
  showArg();
}

f(1); // 1
```

Вызов `showArg()` выведет `1`, получив его из аргументов функции `f`. Функция-стрелка здесь вызвана без параметров, но это не важно: `arguments` всегда берутся из внешней "обычной" функции.

Сохранение внешнего `this` и `arguments` удобно использовать для форвардинга вызовов и создания декораторов.

Например, декоратор `defer(f, ms)` ниже получает функцию `f` и возвращает обёртку вокруг неё, откладывающую вызов на `ms` миллисекунд:

```js run
'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 секунды
```

Аналогичная реализация без функции-стрелки выглядела бы так:

```js
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`.