var functionName = function () {} против функции functionName () {} - PullRequest
6466 голосов
/ 03 декабря 2008

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

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

Два способа:

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

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

Ответы [ 37 ]

4794 голосов
/ 03 декабря 2008

Разница заключается в том, что 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 & mdash; если не действует use strict, в этом случае просто возникает ошибка.

1877 голосов
/ 03 декабря 2008

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

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

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

var xyz = function abc(){};

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

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 ""

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

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

// 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 обрабатывает оба утверждения по-разному. Это объявление функции:

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() & mdash; Ответ: это зависит от браузера.

591 голосов
/ 04 марта 2014

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

Условия:

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

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

  • «Аноним» 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, в спецификации сказано, что делать. Фактически, это дает три отдельных действия:

  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)

"Аноним" function Выражение

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

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

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

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

Именовано 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() звонке? Это функция.

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

  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));

Версия без { ... } называется функцией стрелки с выражением тела или кратким текстом . (Также: A краткая функция стрелки.) Элемент с { ... }, определяющим тело, является функцией стрелки с функцией тела . (Также: 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.

138 голосов
/ 08 августа 2010

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

Тонкое различие между этими двумя способами заключается в том, что при запуске процесса Переменная Instantiation (до фактического выполнения кода) все идентификаторы, объявленные с 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, если указанный идентификатор не найден в цепочке областей действия, он станет deleteable свойством глобального объекта.

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

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

Примечание : Этот ответ был объединен с другим вопросом , в котором основным сомнением и неправильным представлением OP было то, что идентификаторы, объявленные с FunctionDeclaration, не могли быть перезаписано, что не так.

118 голосов
/ 20 апреля 2010

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

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

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

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

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

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

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

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

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();
                            ---------------
88 голосов
/ 03 марта 2011

Другие комментаторы уже рассмотрели семантическое различие двух вариантов выше. Я хотел бы отметить стилистическую разницу: только вариант «назначение» может установить свойство другого объекта.

Я часто строю модули JavaScript с таким шаблоном:

(function(){
    var exports = {};

    function privateUtil() {
            ...
    }

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

    return exports;
})();

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

(Обратите также внимание, что для присваивания требуется точка с запятой после оператора, в то время как объявление запрещает.)

75 голосов
/ 29 марта 2013

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

С

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

, это определение myfunction переопределит любое предыдущее определение, поскольку оно будет выполнено во время разбора.

В то время как

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

выполняет правильную работу по определению myfunction только при соблюдении condition.

60 голосов
/ 08 августа 2010

Важной причиной является добавление одной и только одной переменной в качестве «корня» вашего пространства имен ...

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

}

или

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

Есть много методов для пространства имен. Это становится все более важным с множеством доступных модулей JavaScript.

Также см. Как объявить пространство имен в JavaScript?

53 голосов
/ 25 января 2016

Подъем - это действие интерпретатора JavaScript по перемещению всех объявлений переменных и функций в начало текущей области.

Однако подняты только фактические декларации. оставив задания там, где они есть.

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

Variable

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

global_Page = 10;                                               var global_Page;      « undefined
    « Integer literal, Number Type.   -------------------       global_Page = 10;     « Number         
global_Page = 'Yash';                 |   Interpreted   |       global_Page = 'Yash'; « String
    « String literal, String Type.    «       AS        «       global_Page = true;   « Boolean 
var global_Page = true;               |                 |       global_Page = function (){          « function
    « Boolean Type                    -------------------                 var local_functionblock;  « undefined
global_Page = function (){                                                local_functionblock = 777;« Number
    var local_functionblock = 777;                              };  
    // Assigning function as a data.
};  

Функция

function Identifier_opt ( FormalParameterList_opt ) { 
      FunctionBody | sequence of statements

      « return;  Default undefined
      « return 'some data';
}
  • функции, объявленные внутри страницы, переносятся на верх страницы, имеющей глобальный доступ.
  • функции, объявленные внутри функционального блока, поднимаются наверх блока.
  • Возвращаемое значение функции по умолчанию: « undefined », Переменная Объявление значения по умолчанию также «undefined»

    Scope with respect to function-block global. 
    Scope with respect to page undefined | not available.
    

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

function globalAccess() {                                  function globalAccess() {      
}                                  -------------------     }
globalAccess();                    |                 |     function globalAccess() { « Re-Defined / overridden.
localAccess();                     «   Hoisted  As   «         function localAccess() {
function globalAccess() {          |                 |         }
     localAccess();                -------------------         localAccess(); « function accessed with in globalAccess() only.
     function localAccess() {                              }
     }                                                     globalAccess();
}                                                          localAccess(); « ReferenceError as the function is not defined

Выражение функции

        10;                 « literal
       (10);                « Expression                (10).toString() -> '10'
var a;                      
    a = 10;                 « Expression var              a.toString()  -> '10'
(function invoke() {        « Expression Function
 console.log('Self Invoking');                      (function () {
});                                                               }) () -> 'Self Invoking'

var f; 
    f = function (){        « Expression var Function
    console.log('var Function');                                   f ()  -> 'var Function'
    };

Функция, назначенная переменной Пример:

(function selfExecuting(){
    console.log('IIFE - Immediately-Invoked Function Expression');
}());

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

var namedExpression = function for_InternalUSE(fact){
    if(fact === 1){
        return 1;
    }

    var localExpression = function(){
        console.log('Local to the parent Function Scope');
    };
    globalExpression = function(){ 
        console.log('creates a new global variable, then assigned this function.');
    };

    //return; //undefined.
    return fact * for_InternalUSE( fact - 1);   
};

namedExpression();
globalExpression();

JavaScript интерпретируется как

var anonymous;
var namedExpression;
var globalExpression;

anonymous = function (){
    console.log('anonymous function Expression');
};

namedExpression = function for_InternalUSE(fact){
    var localExpression;

    if(fact === 1){
        return 1;
    }
    localExpression = function(){
        console.log('Local to the parent Function Scope');
    };
    globalExpression = function(){ 
        console.log('creates a new global variable, then assigned this function.');
    };

    return fact * for_InternalUSE( fact - 1);    // DEFAULT UNDEFINED.
};

namedExpression(10);
globalExpression();

Вы можете проверить объявление функции, проверку выражения в разных браузерах, используя jsperf Test Runner


Классы функций конструктора ES5 : Объекты функций, созданные с использованием Function.prototype.bind

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

function Shape(id) { // Function Declaration
    this.id = id;
};
    // Adding a prototyped method to a function.
    Shape.prototype.getID = function () {
        return this.id;
    };
    Shape.prototype.setID = function ( id ) {
        this.id = id;
    };

var expFn = Shape; // Function Expression

var funObj = new Shape( ); // Function Object
funObj.hasOwnProperty('prototype'); // false
funObj.setID( 10 );
console.log( funObj.getID() ); // 10

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

ArrowFunction : ArrowParameters => ConciseBody.

const fn = (item) => { return item & 1 ? 'Odd' : 'Even'; };
console.log( fn(2) ); // Even
console.log( fn(3) ); // Odd
...