Movatterモバイル変換


[0]ホーム

URL:


MDN Web Docs

Эта страница была переведена с английского языка силами сообщества. Вы тоже можете внести свой вклад, присоединившись к русскоязычному сообществу MDN Web Docs.

Функции

Функции в JavaScript

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

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

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

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

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

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

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

Функции вида "function declaration statement"

Объявление функции (function definition, илиfunction declaration, илиfunction statement) состоит из ключевого словаfunction и следующих частей:

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

Например, следующий код объявляет простую функцию с именемsquare:

js
function square(number) {  return number * number;}

Функцияsquare принимает один параметр, названныйnumber. Состоит из одной инструкции, которая означает вернуть параметр этой функции (этоnumber) умноженный на самого себя. Инструкцияreturn указывает на значение, которые будет возвращено функцией.

js
return number * number;

Примитивные параметры (например, число) передаются функции значением;значение передаётся в функцию, но если функция меняет значение параметра,это изменение не отразится глобально или после вызова функции.

Если вы передадите объект как параметр (не примитив, например,массив или определяемые пользователем объекты), и функция изменит свойство переданного в неё объекта, это изменение будет видно и вне функции, как показано в следующем примере:

js
function myFunc(theObject) {  theObject.make = "Toyota";}var mycar = { make: "Honda", model: "Accord", year: 1998 };var x, y;x = mycar.make; // x получает значение "Honda"myFunc(mycar);y = mycar.make; // y получает значение "Toyota"// (свойство было изменено функцией)

Функции вида "function definition expression"

Функция вида "function declaration statement" по синтаксису является инструкцией (statement), ещё функция может быть вида "function definition expression". Такая функция может бытьанонимной (она не имеет имени). Например, функцияsquare может быть вызвана так:

js
var square = function (number) {  return number * number;};var x = square(4); // x получает значение 16

Однако, имя может быть и присвоено для вызова самой себя внутри самой функции и для отладчика (debugger) для идентифицированные функции в стек-треках (stack traces; "trace" — "след" / "отпечаток").

js
var factorial = function fac(n) {  return n < 2 ? 1 : n * fac(n - 1);};console.log(factorial(3));

Функции вида "function definition expression" удобны, когда функция передаётся аргументом другой функции. Следующий пример показывает функциюmap, которая должна получить функцию первым аргументом и массив вторым.

js
function map(f, a) {  var result = [], // Создаём новый массив    i;  for (i = 0; i != a.length; i++) result[i] = f(a[i]);  return result;}

В следующем коде наша функция принимает функцию, которая является function definition expression, и выполняет его для каждого элемента принятого массива вторым аргументом.

js
function map(f, a) {  var result = []; // Создаём новый массив  var i; // Объявляем переменную  for (i = 0; i != a.length; i++) result[i] = f(a[i]);  return result;}var f = function (x) {  return x * x * x;};var numbers = [0, 1, 2, 5, 10];var cube = map(f, numbers);console.log(cube);

Функция возвращает: [0, 1, 8, 125, 1000].

В JavaScript функция может быть объявлена с условием. Например, следующая функция будет присвоена переменнойmyFunc только, еслиnum равно 0:

js
var myFunc;if (num === 0) {  myFunc = function (theObject) {    theObject.make = "Toyota";  };}

В дополнение к объявлениям функций, описанных здесь, вы также можете использовать конструкторFunction для создания функций из строки во время выполнения (runtime), подобноeval().

Метод — это функция, которая является свойством объекта. Узнать больше про объекты и методы можно по ссылке:Работа с объектами.

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

Объявление функции не выполняет её. Объявление функции просто называет функцию и указывает, что делать при вызове функции.

Вызов функции фактически выполняет указанные действия с указанными параметрами. Например, если вы определите функциюsquare, вы можете вызвать её следующим образом:

js
square(5);

Эта инструкция вызывает функцию с аргументом 5. Функция вызывает свои инструкции и возвращает значение 25.

Функции могут быть в области видимости, когда они уже определены, но функции вида "function declaration statement" могут быть подняты (поднятиеhoisting), также как в этом примере:

js
console.log(square(5));/* ... */function square(n) {  return n * n;}

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

Примечание:Это работает только тогда, когда объявлении функции использует вышеупомянутый синтаксис (т.е.function funcName(){}). Код ниже не будет работать. Имеется в виду то, что поднятие функции работает только с function declaration и не работает с function expression.

js
console.log(square); // square поднят со значением undefined.console.log(square(5)); // TypeError: square is not a functionvar square = function (n) {  return n * n;};

Аргументы функции не ограничиваются строками и числами. Вы можете передавать целые объекты в функцию. Функцияshow_props() (объявленная вРабота с объектами) является примером функции, принимающей объекты аргументом.

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

js
function factorial(n) {  if (n === 0 || n === 1) return 1;  else return n * factorial(n - 1);}

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

js
var a, b, c, d, e;a = factorial(1); // a получает значение 1b = factorial(2); // b получает значение 2c = factorial(3); // c получает значение 6d = factorial(4); // d получает значение 24e = factorial(5); // e получает значение 120

Есть другие способы вызвать функцию. Существуют частые случаи, когда функции необходимо вызывать динамически, или поменять номера аргументов функции, или необходимо вызвать функцию с привязкой к определённому контексту. Оказывается, что функции сами по себе являются объектами, и эти объекты в свою очередь имеют методы (посмотрите объектFunction). Один из них это методapply(), использование которого может достигнуть этой цели.

Область видимости функций

(function scope)

Переменные объявленные в функции не могут быть доступными где-нибудь вне этой функции, поэтому переменные (которые нужны именно для функции) объявляют только в scope функции. При этом функция имеет доступ ко всем переменным и функциям, объявленным внутри её scope. Другими словами функция объявленная в глобальном scope имеет доступ ко всем переменным в глобальном scope. Функция объявленная внутри другой функции ещё имеет доступ и ко всем переменным её родительской функции и другим переменным, к которым эта родительская функция имеет доступ.

js
// Следующие переменные объявленны в глобальном scopevar num1 = 20,  num2 = 3,  name = "Chamahk";// Эта функция объявленна в глобальном scopefunction multiply() {  return num1 * num2;}multiply(); // вернёт 60// Пример вложенной функцииfunction getScore() {  var num1 = 2,    num2 = 3;  function add() {    return name + " scored " + (num1 + num2);  }  return add();}getScore(); // вернёт "Chamahk scored 5"

Scope и стек функции

(function stack)

Рекурсия

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

  1. по имени функции
  2. arguments.callee
  3. по переменной, которая ссылается на функцию

Для примера рассмотрим следующие функцию:

js
var foo = function bar() {  // здесь будут выражения};

Внутри функции (function body) все следующие вызовы эквивалентны:

  1. bar()
  2. arguments.callee()
  3. foo()

Функция, которая вызывает саму себя, называетсярекурсивной функцией (recursive function). Получается, что рекурсия аналогична циклу (loop). Оба вызывают некоторый код несколько раз, и оба требуют условия (чтобы избежать бесконечного цикла, вернее бесконечной рекурсии). Например, следующий цикл:

js
var x = 0;while (x < 10) {  // "x < 10" — это условие для цикла  // что-то делаем  x++;}

можно было изменить на рекурсивную функцию и вызовом этой функции:

js
function loop(x) {  if (x >= 10) {    // "x >= 10" — это условие для конца выполнения (тоже самое, что "!(x < 10)")    return;  }  // делать что-то  loop(x + 1); // рекурсионный вызов}loop(0);

Однако некоторые алгоритмы не могут быть простыми повторяющимися циклами. Например, получение всех элементов структуры дерева (например,DOM) проще всего реализуется использованием рекурсии:

js
function walkTree(node) {  if (node == null) return;  // что-то делаем с элементами  for (var i = 0; i < node.childNodes.length; i++) {    walkTree(node.childNodes[i]);  }}

В сравнении с функциейloop, каждый рекурсивный вызов сам вызывает много рекурсивных вызовов.

Также возможно превращение некоторых рекурсивных алгоритмов в нерекурсивные, но часто их логика очень сложна, и для этого потребуется использование стека (stack). По факту рекурсия использует stack: function stack.

Поведение стека можно увидеть в следующем примере:

js
function foo(i) {  if (i < 0) return;  console.log("begin: " + i);  foo(i - 1);  console.log("end: " + i);}foo(3);// Output:// begin: 3// begin: 2// begin: 1// begin: 0// end: 0// end: 1// end: 2// end: 3

Вложенные функции (nested functions) и замыкания (closures)

Вы можете вложить одну функцию в другую. Вложенная функция (nested function;inner) приватная (private) и она помещена в другую функцию (outer). Так образуетсязамыкание (closure). Closure — это выражение (обычно функция), которое может иметь свободные переменные вместе со средой, которая связывает эти переменные (что "закрывает" ("close") выражение).

Поскольку вложенная функция это closure, это означает, что вложенная функция может "унаследовать" (inherit) аргументы и переменные функции, в которую та вложена. Другими словами, вложенная функция содержит scope внешней ("outer") функции.

Подведём итог:

  • Вложенная функция имеет доступ ко всем инструкциям внешней функции.
  • Вложенная функция формирует closure: она может использовать аргументы и переменные внешней функции, в то время как внешняя функция не может использовать аргументы и переменные вложенной функции.

Следующий пример показывает вложенную функцию:

js
function addSquares(a, b) {  function square(x) {    return x * x;  }  return square(a) + square(b);}a = addSquares(2, 3); // возвращает 13b = addSquares(3, 4); // возвращает 25c = addSquares(4, 5); // возвращает 41

Поскольку вложенная функция формирует closure, вы можете вызвать внешнюю функцию и указать аргументы для обоих функций (для outer и innner).

js
function outside(x) {  function inside(y) {    return x + y;  }  return inside;}fn_inside = outside(3); // Думайте об этом как: дайте мне функцию,// которая добавляет 3 к любому введенному значениюresult = fn_inside(5); // возвращает 8result1 = outside(3)(5); // возвращает 8

Сохранение переменных

Обратите внимание, значениеx сохранилось, когда возвращалосьinside. Closure должно сохранять аргументы и переменные во всем scope. Поскольку каждый вызов предоставляет потенциально разные аргументы, создаётся новый closure для каждого вызова во вне. Память может быть очищена только тогда, когдаinside уже возвратился и больше не доступен.

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

Несколько уровней вложенности функций (Multiply-nested functions)

Функции можно вкладывать несколько раз, т.е. функция (A) хранит в себе функцию (B), которая хранит в себе функцию (C). Обе функции B и C формируют closures, так B имеет доступ к переменным и аргументам A, и C имеет такой же доступ к B. В добавок, поскольку C имеет такой доступ к B, который имеет такой же доступ к A, C ещё имеет такой же доступ к A. Таким образом closures может хранить в себе несколько scope; они рекурсивно хранят scope функций, содержащих его. Это называетсяchaining (chain — цепь; Почему названо "chaining" будет объяснено позже)

Рассмотрим следующий пример:

js
function A(x) {  function B(y) {    function C(z) {      console.log(x + y + z);    }    C(3);  }  B(2);}A(1); // в консоле выводится 6 (1 + 2 + 3)

В этом примере C имеет доступ кy функцииB и кx функцииA. Так получается, потому что:

  1. ФункцияB формирует closure, включающееA, т.е.B имеет доступ к аргументам и переменным функцииA.
  2. ФункцияC формирует closure, включающееB.
  3. Раз closure функцииB включаетA, то closureС тоже включает A,C имеет доступ к аргументам и переменным обоих функцийBиA. Другими словами,С связываетцепью (chain) scopes функцийB иA в таком порядке.

В обратном порядке, однако, это не верно.A не имеет доступ к переменным и аргументамC, потому чтоA не имеет такой доступ кB. Таким образом,C остаётся приватным только дляB.

Конфликты имён (Name conflicts)

Когда два аргумента или переменных в scope у closure имеют одинаковые имена, происходитконфликт имени (name conflict). Более вложенный (more inner) scope имеет приоритет, так самый вложенный scope имеет наивысший приоритет, и наоборот. Это цепочка областей видимости (scope chain). Самым первым звеном является самый глубокий scope, и наоборот. Рассмотрим следующие:

js
function outside() {  var x = 5;  function inside(x) {    return x * 2;  }  return inside;}outside()(10); // возвращает 20 вместо 10

Конфликт имени произошёл в инструкцииreturn x * 2 между параметромx функцииinside и переменнойx функцииoutside. Scope chain здесь будет таким: {inside ==>outside ==> глобальный объект (global object)}. Следовательноx функцииinside имеет больший приоритет по сравнению сoutside, и нам вернулось 20 (= 10 * 2), а не 10 (= 5 * 2).

Замыкания

(Closures)

Closures это один из главных особенностей JavaScript. JavaScript разрешает вложенность функций и предоставляет вложенной функции полный доступ ко всем переменным и функциям, объявленным внутри внешней функции (и другим переменным и функции, к которым имеет доступ эта внешняя функция).

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

Также, поскольку вложенная функция имеет доступ к scope внешней функции, переменные и функции, объявленные во внешней функции, будет продолжать существовать и после её выполнения для вложенной функции, если на них и на неё сохранился доступ (имеется ввиду, что переменные, объявленные во внешней функции, сохраняются, только если внутренняя функция обращается к ним).

Closure создаётся, когда вложенная функция как-то стала доступной в неком scope вне внешней функции.

js
var pet = function (name) {  // Внешняя функция объявила переменную "name"  var getName = function () {    return name; // Вложенная функция имеет доступ к "name" внешней функции  };  return getName; // Возвращаем вложенную функцию, тем самым сохраняя доступ  // к ней для другого scope};myPet = pet("Vivie");myPet(); // Возвращается "Vivie",// т.к. даже после выполнения внешней функции// name сохранился для вложенной функции

Более сложный пример представлен ниже. Объект с методами для манипуляции вложенной функции внешней функцией можно вернуть (return).

js
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.getName(); // Viviepet.setName("Oliver");pet.setSex("male");pet.getSex(); // malepet.getName(); // Oliver

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

js
var getCode = (function () {  var apiCode = "0]Eal(eh&2"; // Мы не хотим, чтобы данный код мог быть изменен кем-то извне...  return function () {    return apiCode;  };})();getCode(); // Возвращает apiCode

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

js
var createPet = function (name) {  // Внешняя функция определяет переменную с именем "name".  return {    setName: function (name) {      // Внутренняя функция также определяет переменную с именем "name".      name = name; // Как мы можем получить доступ к "name", определённой во внешней функции?    },  };};

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

Объект arguments функции является псевдо-массивом. Внутри функции вы можете ссылаться к аргументам следующим образом:

js
arguments[i];

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

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

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

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

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

js
// возвращает "red, orange, blue, "myConcat(", ", "red", "orange", "blue");// возвращает "elephant; giraffe; lion; cheetah; "myConcat("; ", "elephant", "giraffe", "lion", "cheetah");// возвращает "sage. basil. oregano. pepper. parsley. "myConcat(". ", "sage", "basil", "oregano", "pepper", "parsley");

Примечание:arguments является псевдо-массивом, но не массивом. Это псевдо-массив, в котором есть пронумерованные индексы и свойствоlength. Однако он не обладает всеми методами массивов.

Рассмотрите объектFunction в JavaScript-справочнике для большей информации.

Параметры функции

Начиная с ECMAScript 2015 появились два новых вида параметров: параметры по умолчанию (default parameters) и остаточные параметры (rest parameters).

Параметры по умолчанию (Default parameters)

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

В прошлом для этого было необходимо в теле функции проверять значения параметров наundefined и в положительном случае менять это значение на дефолтное (default). В следующем примере в случае, если при вызове не предоставили значение дляb, то этим значением станетundefined, тогда результатом вычисленияa * b в функцииmultiply будетNaN. Однако во второй строке мы поймаем это значение:

js
function multiply(a, b) {  b = typeof b !== "undefined" ? b : 1;  return a * b;}multiply(5); // 5

С параметрами по умолчанию проверка наличия значения параметра в теле функции не нужна. Теперь вы можете просто указать значение по умолчанию для параметраb в объявлении функции:

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

Для более детального рассмотрения ознакомьтесь спараметрами по умолчанию.

Остаточные параметры (Rest parameters)

Остаточные параметры предоставляют нам массив неопределённых аргументов. В примере мы используем остаточные параметры, чтобы собрать аргументы с индексами со 2-го до последнего. Затем мы умножим каждый из них на значение первого аргумента. В этом примере используется стрелочная функция (Arrow functions), о которой будет рассказано в следующей секции.

js
function multiply(multiplier, ...theArgs) {  return theArgs.map((x) => multiplier * x);}var arr = multiply(2, 1, 2, 3);console.log(arr); // [2, 4, 6]

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

(Arrow functions)

Стрелочные функции — функции вида "arrow function expression" (неверноfat arrow function) — имеют укороченный синтаксис по сравнению с function expression и лексически связывает значениеthis. Стрелочные функции всегда анонимны. Посмотрите также пост блога hacks.mozilla.org "ES6 In Depth: Arrow functions".

На введение стрелочных функций повлияли два фактора: более короткие функции и лексикаthis.

Более короткие функции

В некоторых функциональных паттернах приветствуется использование более коротких функций. Сравните:

js
var a = ["Hydrogen", "Helium", "Lithium", "Beryllium"];var a2 = a.map(function (s) {  return s.length;});console.log(a2); // выводит [8, 6, 7, 9]var a3 = a.map((s) => s.length);console.log(a3); // выводит [8, 6, 7, 9]

Лексикаthis

До стрелочных функций каждая новая функция определяла своё значениеthis (новый объект в случае конструктора, undefined в strict mode, контекстный объект, если функция вызвана как метод объекта, и т.д.). Это оказалось раздражающим с точки зрения объектно-ориентированного стиля программирования.

js
function Person() {  // Конструктор Person() определяет `this` как самого себя.  this.age = 0;  setInterval(function growUp() {    // Без strict mode функция growUp() определяет `this`    // как global object, который отличается от `this`    // определённого конструктором Person().    this.age++;  }, 1000);}var p = new Person();

В ECMAScript 3/5 эта проблема была исправлена путём присвоения значенияthis переменной, которую можно было бы замкнуть.

js
function Person() {  var self = this; // Некоторые выбирают `that` вместо `self`.  // Выберите что-то одно и будьте последовательны.  self.age = 0;  setInterval(function growUp() {    // Колбэк ссылается на переменную `self`,    // значением которой является ожидаемый объект.    self.age++;  }, 1000);}

Альтернативой может бытьсвязанная функция (bound function), с которой можно правильно вручную определить значениеthis для функцииgrowUp().

В arrow function значениемthis является окружающий его контекст, так следующий код работает ожидаемо:

js
function Person() {  this.age = 0;  setInterval(() => {    this.age++; // |this| должным образом ссылается на объект Person  }, 1000);}var p = new Person();

Далее

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

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

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

Help improve MDN

Learn how to contribute.

This page was last modified on byMDN contributors.


[8]ページ先頭

©2009-2025 Movatter.jp