Вопрос: var functionName = function () {} vs function functionName () {}


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

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

Двумя способами являются:

var functionOne = function() {
    // Some code
};
function functionTwo() {
    // Some code
}

Каковы причины использования этих двух разных методов и каковы плюсы и минусы каждого из них? Есть ли что-то, что можно сделать с помощью одного метода, который нельзя сделать с другим?


5973


источник


Ответы:


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

Например, выражение функции:

// TypeError: functionOne is not a function
functionOne();

var functionOne = function() {
  console.log("Hello!");
};

И объявление функции:

// Outputs: "Hello!"
functionTwo();

function functionTwo() {
  console.log("Hello!");
}

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

if (test) {
   // Error or misbehavior
   function functionThree() { doSomething(); }
}

Вышеописанное фактически определяет functionThreeнезависимо от testценность - если только use strictфактически, и в этом случае он просто вызывает ошибку.


4437



Сначала я хочу исправить Грега: function abc(){}также скопировано - имя abcопределяется в области, где это определение встречается. Пример:

function xyz(){
  function abc(){};
  // abc is defined here...
}
// ...but not here

Во-вторых, можно объединить оба стиля:

var xyz = function abc(){};

xyzбудет определяться как обычно, abcне определено во всех браузерах, но Internet Explorer - не полагайтесь на его определение. Но это будет определено внутри его тела:

var xyz = function abc(){
  // xyz is visible here
  // abc is visible here
}
// xyz is visible here
// abc is undefined here

Если вы хотите использовать псевдонимы во всех браузерах, используйте этот вид объявления:

function abc(){};
var xyz = abc;

В этом случае оба xyzа также abcявляются псевдонимами одного и того же объекта:

console.log(xyz === abc); // prints "true"

Одной из убедительных причин использования комбинированного стиля является атрибут «name» для объектов функций ( не поддерживается Internet Explorer ). В основном, когда вы определяете функцию типа

function abc(){};
console.log(abc.name); // prints "abc"

его имя автоматически назначается. Но когда вы определяете его как

var abc = function(){};
console.log(abc.name); // prints ""

его имя пуст - мы создали анонимную функцию и присвоили ее некоторой переменной.

Еще одна веская причина использовать комбинированный стиль - использовать короткое внутреннее имя для ссылки на себя, предоставляя длинное неконфликтное имя для внешних пользователей:

// Assume really.long.external.scoped is {}
really.long.external.scoped.name = function shortcut(n){
  // Let it call itself recursively:
  shortcut(n - 1);
  // ...
  // Let it pass itself as a callback:
  someFunction(shortcut);
  // ...
}

В приведенном выше примере мы можем сделать то же самое с внешним именем, но оно будет слишком громоздким (и медленнее).

(Другой способ ссылаться на себя - использовать arguments.callee, который все еще относительно длинный и не поддерживается в строгом режиме.)

В глубине JavaScript JavaScript обрабатывает оба утверждения по-разному. Это объявление функции:

function abc(){}

abcздесь определяется везде в текущей области:

// We can call it here
abc(); // Works

// Yet, it is defined down there.
function abc(){}

// We can call it again
abc(); // Works

Кроме того, он поднялся через returnзаявление:

// We can call it here
abc(); // Works
return;
function abc(){}

Это выражение функции:

var xyz = function(){};

xyzздесь определяется из точки назначения:

// We can't call it here
xyz(); // UNDEFINED!!!

// Now it is defined
xyz = function(){}

// We can call it here
xyz(); // works

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

Интересный факт:

var xyz = function abc(){};
console.log(xyz.name); // Prints "abc"

Лично я предпочитаю выражение «выражение функции», потому что таким образом я могу контролировать видимость. Когда я определяю функцию как

var abc = function(){};

Я знаю, что я определил функцию локально. Когда я определяю функцию как

abc = function(){};

Я знаю, что я определил его в глобальном масштабе, учитывая, что я не определял abcгде угодно в цепочке областей. Этот стиль определения устойчив даже при использовании внутри eval(), Хотя определение

function abc(){};

зависит от контекста и может оставить вас гадать, где оно фактически определено, особенно в случае eval()- Ответ: это зависит от браузера.


1783



Вот краткое изложение стандартных форм, которые создают функции: (Первоначально написано для другого вопроса, но адаптировано после того, как он перешел в канонический вопрос).

Сроки:

Быстрый список:

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

  • "Anonymous" functionвыражение (которые, несмотря на термин, иногда создают функции с именами)

  • названный functionвыражение

  • Инициализатор функции доступа (ES5 +)

  • Выражение функции Arrow (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, спецификация говорит, что делать. Фактически, это дает три отдельные вещи:

  1. Если в свободном режиме не в веб-браузере движок JavaScript должен сделать одну вещь
  2. Если в свободном режиме в веб-браузере движок JavaScript должен делать что-то еще
  3. Если в строгий режиме (браузер или нет), механизм 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)

"Anonymous" functionвыражение

Вторая общая форма называется анонимное выражение функции :

var y = function () {
    console.log('y');
};

Как и все выражения, он оценивается, когда он достигается при пошаговом выполнении кода.

В ES5 созданная функция не имеет имени (оно анонимно). В ES2015 функции присваивается имя, если это возможно, вызывая его из контекста. В приведенном выше примере имя будет y, Нечто похожее делается, когда функция является значением инициализатора свойства. (Для получения дополнительной информации о том, когда это происходит, и правила, выполните поиск SetFunctionNameв спецификация - кажется повсюду место.)

названный functionвыражение

Третья форма - это имя функции ( "НФО"):

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,

Выражение функции Arrow (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()вызов? Это функция.

Несколько вещей о функциях стрелок:

  1. У них нет собственных this, Вместо этого они закрыть thisконтекста, в котором они определены. (Они также закрываются argumentsи, при необходимости, super.) Это означает, что thisвнутри них то же самое, что и thisгде они созданы и не могут быть изменены.

  2. Как вы заметили выше, вы не используете ключевое слово 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() {
    }
};

Объявления конструктора и метода в 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,


533



Говоря о глобальном контексте, varзаявление и FunctionDeclarationв конце создаст нестираемой свойство на глобальном объекте, но значение обоих может быть перезаписана ,

Тонкая разница между двумя способами заключается в том, что когда Переменная активация (до фактического выполнения кода) все идентификаторы, объявленные с varбудет инициализирован undefined, и те, которые используются FunctionDeclarationбудут доступны с этого момента, например:

 alert(typeof foo); // 'function', it's already available
 alert(typeof bar); // 'undefined'
 function foo () {}
 var bar = function () {};
 alert(typeof bar); // 'function'

Назначение bar FunctionExpressionпроисходит до времени выполнения.

Глобальная собственность, созданная FunctionDeclarationможет быть перезаписана без каких-либо проблем, как значение переменной, например:

 function test () {}
 test = null;

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

О вашем отредактированном первом примере ( foo = function() { alert('hello!'); };), это незадекларированное задание, я настоятельно рекомендую вам всегда использовать varключевое слово.

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

Кроме того, необъявленные задания бросают ReferenceErrorпо ECMAScript 5 под Строгий режим ,

A должен читать:

Заметка : Этот ответ был объединен с Другой вопрос , в котором основным сомнением и неправильным представлением от ОП было то, что идентификаторы, объявленные с помощью FunctionDeclaration, не может быть перезаписано, что не так.


127



Два фрагмента кода, которые вы разместили там, будут практически одинаковыми для всех целей.

Однако разница в поведении такова, что с первым вариантом ( var functionOne = function() {}), эту функцию можно вызывать только после этой точки в коде.

Со вторым вариантом ( function functionTwo()), функция доступна для кода, который выполняется выше, где объявлена ​​функция.

Это связано с тем, что в первом варианте функция назначается переменной fooво время выполнения. Во втором случае функция присваивается этому идентификатору, foo, во время разбора.

Дополнительная информация

JavaScript имеет три способа определения функций.

  1. В первом фрагменте показан выражение функции , Это предполагает использование оператор «функции» для создания функции - результат этого оператора может быть сохранен в любой переменной или объекте. Выражение функции является таким мощным. Выражение функции часто называют «анонимной функцией», поскольку оно не должно иметь имени,
  2. Второй пример - Объявление функции , Это использует выражение "function" для создания функции. Функция предоставляется во время разбора и может быть вызвана в любом месте этой области. Вы можете сохранить его позже в переменной или объекте.
  3. Третьим способом определения функции является Конструктор "Function ()" , который не показан в вашем исходном сообщении. Не рекомендуется использовать это, поскольку он работает так же, как и eval(), которая имеет свои проблемы.

107



Лучшее объяснение Ответ Грега

functionTwo();
function functionTwo() {
}

Почему нет ошибок? Нам всегда учили, что выражения выполняются сверху донизу (??)

Потому как:

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

Это означает, что такой код:

functionOne();                  ---------------      var functionOne;
                                | is actually |      functionOne();
var functionOne = function(){   | interpreted |-->
};                              |    like     |      functionOne = function(){
                                ---------------      };

Обратите внимание, что часть объявления объявлений не была поднята. Только имя поднимается.

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

functionTwo();              ---------------      function functionTwo() {
                            | is actually |      };
function functionTwo() {    | interpreted |-->
}                           |    like     |      functionTwo();
                            ---------------

89



Other commenters have already covered the semantic difference of the two variants above. I wanted to note a stylistic difference: Only the "assignment" variation can set a property of another object.

I often build JavaScript modules with a pattern like this:

(function(){
    var exports = {};

    function privateUtil() {
            ...
    }

    exports.publicUtil = function() {
            ...
    };

    return exports;
})();

With this pattern, your public functions will all use assignment, while your private functions use declaration.

(Note also that assignment should require a semicolon after the statement, while declaration prohibits it.)


82



An illustration of when to prefer the first method to the second one is when you need to avoid overriding a function's previous definitions.

With

if (condition){
    function myfunction(){
        // Some code
    }
}

, this definition of myfunction will override any previous definition, since it will be done at parse-time.

While

if (condition){
    var myfunction = function (){
        // Some code
    }
}

does the correct job of defining myfunction only when condition is met.


67



An important reason is to add one and only one variable as the "Root" of your namespace...

var MyNamespace = {}
MyNamespace.foo= function() {

}

or

var MyNamespace = {
  foo: function() {
  },
  ...
}

There are many techniques for namespacing. It's become more important with the plethora of JavaScript modules available.

Also see How do I declare a namespace in JavaScript?


54