Вот краткое изложение стандартных форм, которые создают функции: (Первоначально написано для другого вопроса, но адаптировано после перемещения в канонический вопрос.)
Сроки:
Быстрый список:
Объявление функции
«Анонимное» function
выражение (которое, несмотря на термин, иногда создает функции с именами)
Именованное function
выражение
Инициализатор функций доступа (ES5 +)
Выражение функции стрелки (ES2015 +) (которое, как и выражения анонимной функции, не содержит явного имени и все же может создавать функции с именами)
Объявление метода в инициализаторе объекта (ES2015 +)
Объявления конструктора и метода в class
(ES2015 +)
Объявление функции
Первая форма - это объявление функции , которое выглядит так:
function x() {
console.log('x');
}
Объявление функции - это объявление ; это не утверждение или выражение. Таким образом, вы не следуете за ним ;
(хотя это безвредно).
Объявление функции обрабатывается, когда выполнение входит в контекст, в котором оно появляется, перед выполнением любого пошагового кода. Создаваемой ей функции присваивается собственное имя ( x
в примере выше), и это имя помещается в область, в которой появляется объявление.
Поскольку он обрабатывается перед любым пошаговым кодом в том же контексте, вы можете сделать что-то вроде этого:
x(); // Works even though it's above the declaration
function x() {
console.log('x');
}
До ES2015, спецификация не не охватывает то , что двигатель JavaScript должен делать , если поместить объявление функции внутри структуры управления , как try
, if
, switch
, while
и т.д., как это:
if (someCondition) {
function foo() { // <===== HERE THERE
} // <===== BE DRAGONS
}
А поскольку они обрабатываются до запуска пошагового кода, сложно знать, что делать, когда они находятся в структуре управления.
Хотя это не было указано до ES2015, это было допустимое расширение для поддержки объявлений функций в блоках. К сожалению (и неизбежно), разные двигатели делали разные вещи.
Начиная с ES2015, в спецификации сказано, что делать. Фактически, это дает три отдельных действия:
- Если в свободном режиме нет в веб-браузере, движок JavaScript должен делать одно
- Если в свободном режиме в веб-браузере, движок JavaScript должен делать что-то еще
- Если в строгом режиме (браузер или нет), движок JavaScript должен делать еще одну вещь
Правила для свободных режимов хитры, но в строгом режиме объявления функций в блоках просты: они локальны для блока (они имеют область видимости блока , которая также является новой в ES2015), и они поднимаются наверх блока. Так:
"use strict";
if (someCondition) {
foo(); // Works just fine
function foo() {
}
}
console.log(typeof foo); // "undefined" (`foo` is not in scope here
// because it's not in the same block)
«Анонимное» function
выражение
Вторая распространенная форма называется выражением анонимной функции :
var y = function () {
console.log('y');
};
Как и все выражения, оно оценивается, когда достигается при пошаговом выполнении кода.
В ES5 созданная функция не имеет имени (она анонимна). В ES2015, функции по возможности присваивается имя, выводя его из контекста. В приведенном выше примере имя будет y
. Нечто подобное происходит, когда функция является значением инициализатора свойства. (Подробную информацию о том, когда это происходит и правилах, ищите SetFunctionName
в спецификации - она появляется повсюду .)
Именованное function
выражение
Третья форма - это выражение с именованной функцией («NFE»):
var z = function w() {
console.log('zw')
};
Функция, которую она создает, имеет собственное имя ( w
в данном случае). Как и все выражения, это оценивается, когда оно достигается при пошаговом выполнении кода. Имя функции не добавляется в область, в которой появляется выражение; имя находится в области действия самой функции:
var z = function w() {
console.log(typeof w); // "function"
};
console.log(typeof w); // "undefined"
Обратите внимание, что NFE часто являются источником ошибок для реализаций JavaScript. Например, IE8 и более ранние версии обрабатывают NFE совершенно неправильно , создавая две разные функции в два разных момента времени. Ранние версии Safari также имели проблемы. Хорошей новостью является то, что в текущих версиях браузеров (IE9 и выше, текущий Safari) таких проблем больше нет. (Но на момент написания этой статьи, к сожалению, IE8 все еще широко используется, и поэтому использование NFE с кодом для Интернета в целом все еще проблематично.)
Инициализатор функций доступа (ES5 +)
Иногда функции могут проникнуть в значительной степени незамеченными; это в случае с функциями доступа . Вот пример:
var obj = {
value: 0,
get f() {
return this.value;
},
set f(v) {
this.value = v;
}
};
console.log(obj.f); // 0
console.log(typeof obj.f); // "number"
Обратите внимание, что когда я использовал функцию, я не использовал ()
! Это потому, что это функция доступа для свойства. Мы получаем и устанавливаем свойство обычным способом, но за кулисами вызывается функция.
Вы также можете создавать функции доступа с помощью Object.defineProperty
, Object.defineProperties
и менее известный второй аргумент для Object.create
.
Выражение функции стрелки (ES2015 +)
ES2015 приносит нам функцию стрелки . Вот один пример:
var a = [1, 2, 3];
var b = a.map(n => n * 2);
console.log(b.join(", ")); // 2, 4, 6
Видишь эту n => n * 2
вещь, скрывающуюся в map()
звонке? Это функция.
Несколько вещей о функциях стрелок:
У них нет своего this
. Вместо этого, они близко надthis
из контекста , в котором они определены. (Они также закрываются arguments
и, где это уместно super
.) Это означает, что this
внутри них то же самое, что и то, this
где они созданы, и их нельзя изменить.
Как вы уже заметили, вы не используете ключевое слово function
; вместо этого вы используете =>
.
Приведенный n => n * 2
выше пример является одной из них. Если у вас есть несколько аргументов для передачи функции, вы используете parens:
var a = [1, 2, 3];
var b = a.map((n, i) => n * i);
console.log(b.join(", ")); // 0, 2, 6
(Помните, что Array#map
запись передается как первый аргумент, а индекс как второй.)
В обоих случаях тело функции является просто выражением; возвращаемое значение функции будет автоматически результатом этого выражения (вы не используете явное return
).
Если вы делаете больше, чем просто одно выражение, используйте {}
и явное return
(если вам нужно вернуть значение), как обычно:
var a = [
{first: "Joe", last: "Bloggs"},
{first: "Albert", last: "Bloggs"},
{first: "Mary", last: "Albright"}
];
a = a.sort((a, b) => {
var rv = a.last.localeCompare(b.last);
if (rv === 0) {
rv = a.first.localeCompare(b.first);
}
return rv;
});
console.log(JSON.stringify(a));
Версия без { ... }
называется функцией стрелки с телом выражения или кратким телом . (Также: краткая функция стрелки.) Функция с { ... }
определением тела - это функция стрелки с телом функции . (Также: функция многословной стрелки.)
Объявление метода в инициализаторе объекта (ES2015 +)
ES2015 допускает более короткую форму объявления свойства, которое ссылается на функцию, называемую определением метода ; это выглядит так:
var o = {
foo() {
}
};
почти эквивалент в ES5 и более ранних версиях:
var o = {
foo: function foo() {
}
};
Разница (кроме многословия) в том, что метод может использовать super
, а функция - нет. Так, например, если бы у вас был объект, который определил (скажем) valueOf
с использованием синтаксиса метода, его можно было бы использовать super.valueOf()
для получения значения, Object.prototype.valueOf
которое было бы возвращено (прежде чем предположительно делать что-то еще с ним), тогда как вместо этого должна была бы быть версия ES5 Object.prototype.valueOf.call(this)
.
Это также означает, что метод имеет ссылку на объект, для которого он был определен, поэтому, если этот объект является временным (например, вы передаете его Object.assign
как один из исходных объектов), синтаксис метода может означать, что объект сохраняется в памяти, когда в противном случае он мог бы быть сборщиком мусора (если механизм JavaScript не обнаруживает эту ситуацию и не обрабатывает ее, если ни один из методов не использует super
).
Объявления конструктора и метода в class
(ES2015 +)
ES2015 приносит нам class
синтаксис, включая объявленные конструкторы и методы:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return this.firstName + " " + this.lastName;
}
}
Выше приведены два объявления функций: одно для конструктора, который получает имя Person
, и другое для getFullName
, которому назначена функция Person.prototype
.