Please note, this is a STATIC archive of website developer.mozilla.org from 03 Nov 2016, cach3.com does not collect or store any user information, there is no "phishing" involved.

Перевод не завершен. Пожалуйста, помогите перевести эту статью с английского.

Функции в JavaScript

Функции - ключевая концепция в JavaScript. Важнейшей особенностью языка является первоклассная поддержка функций (functions as first-class citizen). Любая функция это объект, и следовательно ею можно манипулировать как объектом, в частности:

  • передавать как аргумент и возвращать в качестве результата при вызове других функций (функций высшего порядка).
  • создавать анонимно и присваивать в качестве значений переменных или свойств объектов

Это определяет высокую выразительную мощность JavaScript и позволяет относить его к числу языков, реализующих функциональную парадигму программирования (что само по себе есть очень круто по многим соображениям).

Функция в JavaScript
специальный тип объектов, позволяющий формализовать средствами языка определённую логику поведения и обработки данных. 

Для понимания работы функций необходимо (и достаточно?) иметь представление о следующих моментах:

  • способы объявления 
  • способы вызова 
  • параметры и аргументы вызова (arguments)
  • область данных (Scope) и замыкания (Closures)
  • объект привязки (this)
  • возращаемое значение (return)
  • исключения (throw)
  • использование в качестве конструктора объектов
  • сборщик мусора (garbage collector)

Объявление функций

Функция (как и всякий объект) должна быть объявлена(определена, define) перед её использованием. 

Объявление(определение) функции - указание сигнатуры и тела функции:

  • сигнатура - имя(необязательно) и список входных формальных параметров 
  • тело функции - комбинация управляющих конструкций и выражений языка над внешними и локальными данными

Объявление пользовательской функции всегда находится в теле некоторой внешней функции-контейнера, которая, в свою очередь, возможно также вложена в некоторую функцию. Цепочка всех таких внешних функций, вложенных друг в друга, образует лексический диапазон функции. 

Во избежание оговорок о глобальных переменных и функциях, удобно полагать, что программа на языке JavaScript представляет собой тело неявной функции [[ main]]().

Существует три способа объявить функцию:

В декларативном стиле

Для декларативного объявления функции используется синтаксическая конструкция

function идентификатор(параметры) {

  инструкции

  return выражение

}
  • Ключевое слово function
  • Идентификатор (обязательно).
  • Список имён формальных параметров (и значений по умолчанию) в круглых скобках разделенных запятыми
  • тело функции в фигурных скобках вида {}.

Пример. Следующий код объявляет функцию  с именем square и параметром number; тело состоит из инструкции return и выражения, которое дословно формализуют следующую логику: "вернуть результат произведения аргумента  number на самого себя". :

function square(number) {

  return number * number;
}

Особенностью декларативного объявления является его "всплытие"(hoisting) в начало функции, независимо от того в каком месте контейтера оно находится. 

В примере ниже декларативное объявление функции находится после вызова:

{
  print(square(5));

  // инициализация "всплывает" вместе с декларацией переменной square
  // Аналогичный код в функциональном стиле работать не будет
  function square(n){return n*n}
} 

В функциональном стиле

Функции также могут быть созданы внутри выражения. Такие функции, как правило, анонимны:

var square = function(number) {
  return number * number;
}

Но могут иметь определённое имя. (это имя удобно использовать для рекурсии, или в отладчике(debugger)):

var factorial = function fac(n) {return n<2 ? 1 : n*fac(n-1)};

print(factorial(3));

Cтрелочные функции

Современный стандарт языка поддерживает аномимные стрелочные функции(fat arrow function).

(param1, param2, …, paramN) => { statements }
(param1, param2, …, paramN) => expression

Они особенно удобны, когда надо задать аргумент для функции высшего порядка:

[0, 1, 2, 5, 10].map((x) => x * x * x); // вернет [0, 1, 8, 125, 1000].

Помимо упрощённого синтаксиса, такие функции всегда неявно привязываются в МОМЕНТ ОБЪЯВЛЕНИЯ к текущему лексическому контексту выполнения:

function Person(){
  this.age = 0;

  setInterval(() => {
    this.age++; // в данном случае this будет ссылаться на создаваемый объект obj, а не на window
  }, 1000);
}

var obj = new Person();

Полное описание в статье справочника Functions/Arrow_functions

Смотри также статью на hacks.mozilla.org: "ES6 In Depth: Arrow functions".

В стиле ООП

Учитывая то, что функция по сути является объектом, можно использовать оператор new и Function конструктор чтобы создавать функции динамически во время выполнения (подобно тому как это делает eval()). 

Однако такого подхода следует избегать из соображений производительности и безопасности.

var powerOfFive = new Function('x',
                  'return ' + Array(5).map(()=>'x').join('*'));

Методы

Функции очень часто используются как методы объектов, реализующих ООП. 

Метод 
это функция, заданная как значение свойства объекта.

Специальный синтаксис вызова методов позволяет неявно передавать объект в качестве привязки(this).

class Greeting{

  constructor(prefix){
    this.prefix = prefix;
  }

  // это метод:
  hello(name){
    return `${this.prefix}, ${name}`;
  }
}

var obj = new Greeting("Привет");

// вызов метода (obj передаётся в качестве контекста `this`)
obj.hello('Вася');

Больше информации об объектах и методах в Работа с Объектами.

Вызов функций

Помимо манипуляций c ними как с обычными объектами, функции можно вызывать - запустить процесс вычисления выражений над данными

Вызов (call) функции
последовательное выполнение управляющий инструкций и выражений из тела функции применительно к входным данным и контексту. 
Выражение (expression) 
комбинация математических и специальных операций, а также вызовов функций, которые описывают способ вычисления результирующего значения(result) в зависимости от входящих данных(input). 

В момент вызова фунции могут быть переданы 

  • входные данные в виде списка аргументов-значений,
  • а также объект привязки - специальный аргумент, именуемый ключевым словом this.

Кроме этого, выражения в функции могут адресоваться:

  • к константам (строковые литералы, числа и т.д.)
  • к локальным переменным,
  • а также к внешним свободным переменным по цепочке замыкания.

В результате выполнения функция

  • возвращает некоторое значение (явно с помощью return или неявно) 
  • или бросает исключение.

Например, вызвать  square() можно следующим образом:

// Функция выполняет свои инструкции над аргументом 5 
// и возвращает значение-результат 25

var result = square(5);

Вызов функции должен быть выполнен в пределах её области видимости после того как функция объявлена. Область видимости функции определяется точно также как и для переменной любого другого типа.

Рекурсия

Рекурсивная функция 
функция, содержащая вызов самой себя непосредственно в своём теле либо через другие функции.
Классический пример рекурсивной функции вычисляющей факториал:
function factorial(n) {
  if ((n == 0) || (n == 1))
    return 1;
  else
    return (n * factorial(n - 1));
}

//вычислить факториал пяти:
var e = factorial(5); // e будет равно 120

Вызов с помощью .apply() и .call()

Существуют и другие способы вызывать функции. Поскольку функции сами являются объектами, они содержат собственные методы. В частности, метод apply(), может использоваться, например, когда нужно адресоваться к функции динамически, или передать различное количество аргументов, или явно указать контекст.

var fn = resolveAction();
fn.apply(context, [number1, number2]);

Смотрите Function объект для более детального понимания.

Параметры и аргументы вызова

Параметры функции
список идентификаторов (и возможно сопоставленных им значений по умолчанию и типов данных), заданный В МОМЕНТ ОБЪЯВЛЕНИЯ.
Параметры удобно рассматривать как объявления локальных переменных, каждая из которых инициализируются в МОМЕНТ ВЫЗОВА соответствующим значением из списка аргументов (или значением по умолчанию).
Аргументы(входные данные) функции
произвольный список значений, передаваемых функции В МОМЕНТ ВЫЗОВА.

Соответствие имён параметров и значений аргументов задано тупо порядком их перечисления при обявлении/вызове.

Параметры в  Javascript никак не ограничивают ни количество, ни содержание аргументов.

 

По значению или по ссылке

Типы аргументов функции могут быть как примитивами (строки, числа, логические(boolean)), так и объектами (включая Array или функции):

  • Значения-примитивы передаются в функции по значению: значение КОПИРУЕТСЯ, так что если функция изменит значение параметра, это изменение не будет иметь внешнего эффекта.
  • объекты передаются в фунцию по ссылке:  переприсваивание самой ссылки также не имеет внешнего эффекта, НО если функция изменяет свойства объекта, то эти изменения будут видимы вне функции (побочный эффект).

пример:  Стрёмный побочный эффект от изменения свойств объекта-аргумента:

function myFunc(patient) {
  patient.gender= "F";
}

var he = {gender: "M"};

myFunc(he);

var y = he.gender;     // y gets the value "F", oops

Назначение параметру новой ссылки на объект не имеет влияния вне функции. Это продемонстрировано в примере ниже:

function myFunc(theObject) {
  theObject = {make: "Ford", model: "Focus", year: 2006};
}

var mycar = {make: "Honda", model: "Accord", year: 1998},
    x,
    y;

x = mycar.make;     // x gets the value "Honda"

myFunc(mycar);
y = mycar.make;     // y still gets the value "Honda" 

В первом случае, объект mycar был передан в функцию myFunc, которая изменила его состояние. Во втором случае, функция не изменяла переданный объект; вместо этого, она создавала новую локальную переменную с тем же именем что и имя параметра функции, так что никак не влияет на глобальный объект передаваемый в функцию.

Использование объекта arguments

Доступ к аргументам может быть получен непосредственно (без обращения по именам объявленных параметров).

Ключевое слово arguments адресует структуру, где содержится список входных аргументов в порядке передачи при вызове. Общее количество аргументов содержится в arguments.length, а значение отдельного аргумента можно получить как arguments[i], где i - порядковый номер аргумента начиная с нуля. Таким образом, например, первый аргумент переданный в функцию будет arguments[0]. .

Используя объект arguments, можно адресоваться к большему количеству аргументов, чем это описано в параметрах функции. Это часто полезно, если вы не знаете заранее как много аргументов будет передаваться в функцию. 

Заметьте, что структура arguments крякает как утка похожа на массив, но массивом не является.

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

function myConcat(separator) {
   var result = ""; // initialize list
       
   // iterate through arguments
   for (var i = 1; i < arguments.length; i++) {
      result += arguments[i] + separator;
   }
   return result;
}

// returns "red, orange, blue, " 
myConcat(", ", "red", "orange", "blue"); 

// returns "elephant; giraffe; lion; cheetah; " 
myConcat("; ", "elephant", "giraffe", "lion", "cheetah"); 

Аргументы и оператор развёртки

Современный стандарт языка позволяет в элегантной функциональной манере отказаться от arguments, заменив его оператором развёртки(spread) (...) :

const myConcat = (sep, ...strings) => strings.join(sep) // !!!

Значения по умолчанию

В JavaScript, значения параметров по умолчанию устанавливаются в undefined.

В определённых ситуациях было бы удобно назначать другие значения по умолчанию. В прошлом, общий подход состоял в ручной проверке значения параметра на undefined и в зависимости от результата, установки более подходящего значения по умолчанию:

function multiply(a, b) {
  b = typeof b !== 'undefined' ?  b : 1;

  return a*b;
}

multiply(5); // 5

Современный стандарт языка вводит прекрасную возможность явного указания значения по умолчанию с помощью выражения при ОБЪЯВЛЕНИИ функции:

function multiply(a, b = 1) {
  return a*b;
}

multiply(5); // 5

Заметим что вычисление значения по умолчанию будет происходить В МОМЕНТ ВЫЗОВА функции 

Также будет нелишним заметить что такие выражения могут адресоваться к предыдущим параметрам:

function multiply(a, b = 2*a) { return a*b; } 

multiply(5); // 50

Детальная информация в статье справочника default parameters 

Область данных и замыкания

В момент вычисления выражений имена переменных необходимо подменить соответствующими значениями.

Для этих целей в JavaScript реализован  механизм разрешения имён на основе области данных(Scope) и замыканий(Closure):

var prefix = "Привет";
function sayHi(name) {
  var phrase = prefix + ", " + name;
  alert( phrase );
}
// логика разрешения переменных
RESOLVE = (env, id) => {
 if (env[id]) return env[id];
 if (env.OUTER_SCOPE) return RESOLVE(env.OUTER_SCOPE, id);
 throw new Error('ReferenceError: variable %id is not declared')
}
...
// внешний область данных, актуальный на момент создания функции
SCOPE = { prefix: "Привет" , sayHi: undefined} 
...
// замыкание = функция + внешнее окружение
var sayHi = { 
 OUTER_SCOPE: SCOPE, //захвачен в момент создания функции
 PARAMETERS:['name'] 
 BODY: ()=>{

  // внутренняя область данных, создаваемая неявно в момент вызова
  SCOPE = { 
    name: 'Вася', 
    phrase: undefined,
    OUTER_SCOPE: sayHi.OUTER_SCOPE // образуем цепочку 
  }

  var phrase = 
             RESOLVE(SCOPE,'prefix') // будет извлечён из внешней области
             + ", "
             + RESOLVE(SCOPE,'name'); // будет извлечён из внутренней области

  alert( RESOLVE(SCOPE,'phrase' );

 }
}

// передаётся контекст null и список аргументов
CALL_FUNCTION(sayHi, null, ['Вася']);

1) Каждый раз В МОМЕНТ ВЫЗОВА функции, происходит неявное создание нового экземпляра области данных(Scope) - специального объекта , где ДО НАЧАЛА ВЫПОЛНЕНИЯ(hoisting) регистрируются

  • переменные из списка параметров функции, проинициализированные соответствующими аргументами-значениями.
  • переменные, явно объявленные в теле функции с помощью var
  • вложенные функции, объявленные в теле в декларативном стиле
  • неявная ссылка-замыкание[[OuterScope]] данной функции (образуя цепочку замыканий лексического диапазона)

2) В МОМЕНТ ОБЪЯВЛЕНИЯ объект-функция неявно получает постоянную ссылку [[OuterScope]] на актуальную область переменных  выполняющейся функции-контейнера.

Эта ссылка (или в более общем смысле - связка функция + ссылка) называется замыканием. Она позволяет адресоваться из тела функции  к внешним(свободным) переменным.

3) Если искомое имя переменной не обнаружено в собственной области переменных, то поиск продолжается последовательно вдоль по цепочке замыканий(scope chain) пока имя не будет найдено (в противном случае сработает исключение  ReferenceError).

В спецификации ECMA-262,  присутствует гораздо больше деталей, в частности речь идёт о двух объектах: VariableEnvironment и LexicalEnvironment. Но мы абстрагируемся и используем везде термин Scope, это позволяет достаточно точно описать происходящее.

Более формальное описание находится в спецификации ECMA-262, секции 10.2-10.5 и 13.

Таким образом, общее правило видимости и доступности переменных таково

 Локальные переменные и параметры функции, видны ТОЛЬКО в пределах этой функции и внутри всех вложенных функций на всю глубину.

Они доступны на протяжении всего времени существования функций, которые к ним обращаются.

Замыкание продожает существовать пока существует сама функция (несмотря на то, что внешняя функция возможно уже завершила выполнение и снесена сборщиком мусора).

Таким образом, время жизни локальных переменных функции определяется временем выполнения/использования этой функции и всех вложенных функций.

Современный стандарт языка позволяет определять локальные переменые в рамках отдельно взятого блока кода с помощью let и const. В этом случае, все правила продолжают работать как если бы каждый блок кода был неявной вложенной функцией.

пример:  Вложенная функция incrementor обращается к параметру base внешней функции factory (замыкание продолжает существовать после завершения внешней функции):

function factory (base) {
  
  // returning function has access to external `base` parameter  
  return function incrementor(inc) { return base + inc; };
}

var sum2 = factory(2);

sum2(5); // Returns 7

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

var createPet = function(name) {
  var sex;
  
  return {
    setName: function(newName) {
      name = newName;
    },
    
    getName: function() {
      return name;
    },
    
    getSex: function() {
      return sex;
    },
    
    setSex: function(newSex) {
      if(typeof newSex == "string" && (newSex.toLowerCase() == "male" || newSex.toLowerCase() == "female")) {
        sex = newSex;
      }
    }
  }
}

var pet = createPet("Vivie"); 
pet.setSex("female"); 
pet.getName(); // Vivie
pet.getSex(); // female 

var pet2 = createPet("Oliver"); 
pet2.setSex("male"); 
pet2.getName(); // Oliver
pet2.getSex();  // male
                  

Переменные внутренней функции могут использоваться в качестве безопасных хранилищ для закрытых(private) данных:

var getCode = (function(){
  // никто не попробует сахарку
  var secureCode = "сахарок";
​ 
  return function (token) {
    return mix(secureCode, token);
  };
})();

getCode();    // Returns the secret code

Если замкнутая функция определяет переменную с тем же именем что и переменная во внешней области видимости, то становится невозможным более получить ссылку на переменную во внешней области видимости.

Пример со перекрытием имён переменных внешней и внутренней функций

var createPet = function(name) {  // Outer function defines a variable called "name"
  return {
    setName: function(name) {    // Enclosed function also defines a variable called "name"
      name = name;               // ??? How do we access the "name" defined by the outer function ???
    }
  }
}

Объект привязки (this)

объект привязки 
специальный аргумент, именуемый ключевым словом this.

Основное назначение объекта привязки состоит в поддержке вызовов методов объектов в стиле ООП (см. выше).

// для методов объекта 
obj.method(param); // ->  obj.method.call(obj, param)

// или для внешних функций:
var obj = {}, fn = function(){};
obj::fn(param) // -> fn.call(obj, param)

Ссылка на объект привязки this передаётся

  • для обычных функций - каждый раз В МОМЕНТ ВЫЗОВА.
  • для стрелочных функций - связывается в МОМЕНТ ОБЪЯВЛЕНИЯ

Стандарт языка предоставляет средства явного связывания 

var obj = { some : function (){...} }

var boundSome = some.bind(obj);

// или ещё проще:

var boundSome2 = ::obj.some;

Далее

Подробное техническое описание функций в статье справочника Функции

Смотрите также Function в Справочнике JavaScript для получения дополнительной информации по функции как объекту.

Внешние ресурсы:

Метки документа и участники

 Внесли вклад в эту страницу: Mainstand, Grumvol, DeekHalden, alitskevich, keffidesign, JuGeer, serhiyv, pashutk, roma-derski, fscholz, andrcmdr, dixon2002, teoli, uleming
 Обновлялась последний раз: Mainstand,