Сравнение: var functionName = function() {} против function functionName() {}
Я недавно начал поддерживать код JavaScript, написанный другим разработчиком. Я исправляю ошибки, добавляю новые функции и стараюсь упорядочить код, сделать его более последовательным.
Предыдущий разработчик использовал два способа объявления функций, и я не могу понять, есть ли в этом какая-то причина или нет.
Два способа выглядят так:
var functionOne = function() {
// Некоторый код
};
И
function functionTwo() {
// Некоторый код
}
В чем причины использования этих двух разных методов, и каковы их плюсы и минусы? Есть ли что-то, что можно сделать с одним методом, чего нельзя сделать с другим?
5 ответ(ов)
Прежде всего, я хочу поправить Грега: function abc(){}
тоже имеет область видимости — имя abc
определяется в той области, где это определение встречается. Например:
function xyz(){
function abc(){};
// abc определена здесь...
}
// ...но не здесь
Во-вторых, можно объединить оба стиля:
var xyz = function abc(){};
xyz
будет определено как обычно, а abc
будет неопределенным во всех браузерах, кроме Internet Explorer — не рассчитывайте на то, что оно будет определено. Тем не менее, оно будет определено внутри тела функции:
var xyz = function abc(){
// xyz здесь виден
// abc здесь виден
}
// xyz здесь виден
// abc неопределено здесь
Если вы хотите создавать псевдонимы для функций во всех браузерах, используйте такое объявление:
function abc(){};
var xyz = abc;
В этом случае xyz
и abc
будут псевдонимами одного и того же объекта:
console.log(xyz === abc); // выводит "true"
Одной из убедительных причин использовать комбинированный стиль является атрибут "name" объектам функций (не поддерживается Internet Explorer). Обычно, когда вы определяете функцию так:
function abc(){};
console.log(abc.name); // выводит "abc"
имя автоматически присваивается. Но когда вы определяете её так:
var abc = function(){};
console.log(abc.name); // выводит ""
имя будет пустым — мы создали анонимную функцию и присвоили её переменной.
Еще одной хорошей причиной использовать комбинированный стиль является возможность использовать короткое внутреннее имя для самореференции, при этом предоставляя длинное, не конфликтующее имя для внешних пользователей:
// Предположим, что really.long.external.scoped равно {}
really.long.external.scoped.name = function shortcut(n){
// Позволяем ей вызывать себя рекурсивно:
shortcut(n - 1);
// ...
// Позволяем передавать себя как колбэк:
someFunction(shortcut);
// ...
}
В приведенном выше примере мы можем сделать то же самое с внешним именем, но это будет слишком громоздко (и медленнее).
(Другой способ ссылаться на себя — использовать arguments.callee
, который всё ещё относительно длинный и не поддерживается в строгом режиме.)
В глубине JavaScript обрабатывает оба выражения по-разному. Это объявление функции:
function abc(){}
abc
здесь определяется повсюду в текущей области видимости:
// Мы можем вызвать её здесь
abc(); // Работает
// Тем не менее, она определяется внизу.
function abc(){}
// Мы можем снова вызвать её
abc(); // Работает
Также, она будет "поднята" через оператор return
:
// Мы можем вызвать её здесь
abc(); // Работает
return;
function abc(){}
А это выражение функции:
var xyz = function(){};
xyz
здесь определяется с момента присвоения:
// Мы не можем вызвать её здесь
xyz(); // UNDEFINED!!!
// Теперь она определена
xyz = function(){}
// Мы можем вызвать её здесь
xyz(); // работает
Различие между объявлением функции и выражением функции — это настоящая причина, по которой существует разница, продемонстрированная Грегом.
Интересный факт:
var xyz = function abc(){};
console.log(xyz.name); // Выводит "abc"
Лично я предпочитаю объявление в виде "выражения функции", потому что таким образом я могу контролировать видимость. Когда я определяю функцию так:
var abc = function(){};
Я знаю, что определил функцию локально. Когда я определяю функцию так:
abc = function(){};
Я понимаю, что при условии, что я не определил abc
нигде в цепочке областей видимости, я определил её глобально. Этот стиль определения устойчив даже при использовании внутри eval()
. В то время как определение:
function abc(){};
зависит от контекста и может оставить вас в недоумении относительно того, где она на самом деле определена, особенно в случае eval()
— ответ таков: это зависит от браузера.
Два фрагмента кода, которые вы привели, будут вести себя практически одинаково во всех случаях.
Однако есть разница в поведении: в первом варианте (var functionOne = function() {}
) функция может быть вызвана только после того, как она была определена в коде.
Во втором варианте (function functionTwo() {}
) функция доступна для кода, выполняемого выше места, где она была объявлена.
Это происходит потому, что в первом случае функция присваивается переменной functionOne
во время выполнения. Во втором же случае функция связывается с идентификатором (в данном случае functionTwo
) во время разбора (парсинга) кода.
Более техническая информация
В JavaScript существует три способа определения функций:
Ваш первый фрагмент демонстрирует функциональное выражение. Это означает использование оператора "function" для создания функции — результат этого оператора может быть сохранён в любой переменной или свойстве объекта. Функциональное выражение считается мощным, так как оно часто называется "анонимной функцией", поскольку может не иметь имени.
Ваш второй пример представляет собой объявление функции. Оно использует "function" для создания функции. Эта функция становится доступной во время разбора кода и может быть вызвана в любом месте этого контекста. Вы всё еще можете сохранить её в переменной или свойстве объекта позже.
Третий способ определения функции — это конструктор "Function()", который не был показан в вашем оригинальном вопросе. Его не рекомендуется использовать, так как он работает аналогично
eval()
, у которого есть свои проблемы.
Другие комментаторы уже подробно рассмотрели семантическую разницу между двумя приведенными вариантами. Я хотел бы отметить стилистическую разницу: только вариант с "назначением" может устанавливать свойства другого объекта.
Я часто создаю модули JavaScript по следующему паттерну:
(function(){
var exports = {};
function privateUtil() {
...
}
exports.publicUtil = function() {
...
};
return exports;
})();
С этим паттерном ваши публичные функции будут использовать назначение, в то время как ваши приватные функции будут использовать декларацию.
Также обратите внимание, что после оператора назначения требуется точка с запятой, тогда как декларация не допускает этого.
Когда стоит предпочесть первый способ второму, так это в тех случаях, когда необходимо избежать переопределения предыдущих определений функции.
В случае первого варианта:
if (condition) {
function myfunction() {
// Некоторый код
}
}
Это определение myfunction
переопределит любое предыдущее определение, так как оно выполняется во время парсинга (parse-time).
Второй вариант, где вместо этого используется присвоение:
if (condition) {
var myfunction = function() {
// Некоторый код
}
}
делает все правильно, так как myfunction
будет определена только если выполнено условие condition
. Таким образом, использование первого метода предпочтительнее в ситуациях, когда необходимо сохранять предыдущее определение функции.
Объявление функции и выражение функции, присвоенное переменной, ведут себя одинаково после того, как связывание установлено.
Однако существует разница в том, как и когда объект функции фактически ассоциируется с его переменной. Эта разница связана с механизмом, называемым подъемом переменных (hoisting) в JavaScript.
В общем, все объявления функций и переменных поднимаются на верх функции, в которой они объявлены (поэтому мы говорим, что JavaScript обладает функциональной областью видимости).
- Когда объявление функции поднимается, тело функции "следует" за ним, так что когда тело функции оценивается, переменная немедленно связывается с объектом функции.
- Когда объявление переменной поднимается, инициализация не следует за ним, а "остаётся позади". Переменная инициализируется значением
undefined
в начале тела функции и получит значение в исходном месте в коде. (На самом деле, она получит значение в каждом месте, где происходит объявление переменной с тем же именем.)
Также важно учитывать порядок подъема: объявления функций имеют приоритет над объявлениями переменных с тем же именем, а последнее объявление функции имеет приоритет над предыдущими объявлениями функции с тем же именем.
Примеры...
var foo = 1;
function bar() {
if (!foo) {
var foo = 10 }
return foo; }
bar() // 10
Переменная foo
поднимается в верхнюю часть функции, инициализируется значением undefined
, так что !foo
будет true
, и foo
присваивается значение 10
. foo
вне области видимости bar
не играет роли и остаётся нетронутым.
function f() {
return a;
function a() {return 1};
var a = 4;
function a() {return 2}}
f()() // 2
В этом случае объявление функции имеет приоритет над переменной, и последнее объявление функции "остается".
function f() {
var a = 4;
function a() {return 1};
function a() {return 2};
return a; }
f() // 4
Здесь a
инициализируется объектом функции, полученным в результате оценки второго объявления функции, и затем ей присваивается значение 4
.
var a = 1;
function b() {
a = 10;
return;
function a {}}
b();
a // 1
В этом случае объявление функции поднимается первым, объявляя и инициализируя переменную a
. Затем этой переменной присваивается значение 10
. Иными словами: присваивание не влияет на внешнюю переменную a
.
Установка значения по умолчанию для параметра функции в JavaScript
В чем разница между call и apply?
Проверка существования переменной в JavaScript (определена/инициализирована)
JavaScript: Знак плюс перед функциональным выражением
Как преобразовать Set в Array?