Vraag var functionName = function () {} vs function functionName () {}


Ik ben onlangs begonnen met het onderhouden van de JavaScript-code van iemand anders. Ik repareer bugs, voeg functies toe en probeer de code op te ruimen en consistenter te maken.

De vorige ontwikkelaar gebruikt twee manieren om functies te declareren en ik kan er niet achter komen als er een reden voor is of niet.

De twee manieren zijn:

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

Wat zijn de redenen voor het gebruik van deze twee verschillende methoden en wat zijn de voor- en nadelen van elk ervan? Is er iets dat kan worden gedaan met de ene methode die niet met de andere kan worden gedaan?


6052
2017-12-03 11:31


oorsprong


antwoorden:


Het verschil is dat functionOne is een functie-expressie en dus alleen gedefinieerd wanneer die regel wordt bereikt, terwijl functionTwo is een functieverklaring en wordt gedefinieerd zodra de omringende functie of het script wordt uitgevoerd (vanwege hijsen).

Bijvoorbeeld een functie-uitdrukking:

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

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

En, een functie-verklaring:

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

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

Dit betekent ook dat u functies niet conditioneel kunt definiëren met behulp van functieverklaringen:

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

Het bovenstaande definieert eigenlijk functionThree ongeacht testwaarde - tenzij use strict is in feite, in welk geval het eenvoudig een fout oproept.


4489
2017-12-03 11:37



Eerst wil ik Greg corrigeren: function abc(){} is ook scoped - de naam abc wordt gedefinieerd in de scope waarin deze definitie wordt aangetroffen. Voorbeeld:

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

Ten tweede is het mogelijk om beide stijlen te combineren:

var xyz = function abc(){};

xyz wordt zoals gebruikelijk gedefinieerd, abc is niet gedefinieerd in alle browsers, maar in Internet Explorer - vertrouw er niet op dat het wordt gedefinieerd. Maar het zal binnen zijn lichaam worden gedefinieerd:

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

Als je alias-functies in alle browsers wilt gebruiken, gebruik je dit soort verklaring:

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

In dit geval allebei xyz en abc zijn aliassen van hetzelfde object:

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

Een dwingende reden om de gecombineerde stijl te gebruiken is het "naam" -attribuut van functieobjecten (niet ondersteund door Internet Explorer). In principe wanneer u een functie zoals definieert

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

de naam wordt automatisch toegewezen. Maar als je het definieert zoals

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

de naam is leeg - we hebben een anonieme functie gemaakt en deze aan een variabele toegewezen.

Een andere goede reden om de gecombineerde stijl te gebruiken, is om een ​​korte interne naam te gebruiken om naar zichzelf te verwijzen, terwijl het een lange niet-conflicterende naam biedt voor externe gebruikers:

// 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);
  // ...
}

In het bovenstaande voorbeeld kunnen we hetzelfde doen met een externe naam, maar het zal te log zijn (en langzamer).

(Een andere manier om naar zichzelf te verwijzen is om te gebruiken arguments.callee, die nog steeds relatief lang is, en niet wordt ondersteund in de strikte modus.)

Diep vanbinnen behandelt JavaScript beide instructies anders. Dit is een functieverklaring:

function abc(){}

abc hier wordt overal in de huidige scope gedefinieerd:

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

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

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

Ook hij gehesen door een return uitspraak:

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

Dit is een functie-expressie:

var xyz = function(){};

xyz hier wordt gedefinieerd vanaf het punt van toewijzing:

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

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

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

Functie-declaratie versus functie-expressie is de echte reden waarom er een verschil is aangetoond door Greg.

Leuk weetje:

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

Persoonlijk geef ik de voorkeur aan de verklaring "functie-expressie" omdat ik op deze manier de zichtbaarheid kan regelen. Wanneer ik de functie als definieer

var abc = function(){};

Ik weet dat ik de functie lokaal heb gedefinieerd. Wanneer ik de functie als definieer

abc = function(){};

Ik weet dat ik het globaal heb gedefinieerd op voorwaarde dat ik het niet heb gedefinieerd abc overal in de keten van scopes. Deze stijl van definitie is veerkrachtig, zelfs wanneer ze binnen wordt gebruikt eval(). Terwijl de definitie

function abc(){};

hangt af van de context en laat je raden waar het daadwerkelijk is gedefinieerd, vooral in het geval van eval() - het antwoord is: het hangt van de browser af.


1804
2017-12-03 17:43



Dit is het overzicht op de standaardformulieren die functies maken: (Oorspronkelijk geschreven voor een andere vraag, maar aangepast nadat deze is verplaatst naar de canonieke vraag.)

Voorwaarden:

De snelle lijst:

  • Functie verklaring

  • "Anoniem" function Uitdrukking (die ondanks de term soms functies maken met namen)

  • Genaamd function Uitdrukking

  • Accessor-functie-initialisatie (ES5 +)

  • Pijlfunctie-expressie (ES2015 +) (die, net als anonieme functie-uitdrukkingen, geen expliciete naam bevatten en toch functies met namen kunnen maken)

  • Methodeverklaring in Object Initializer (ES2015 +)

  • Aannemers- en methodenverklaringen in class (ES2015 +)

Functie verklaring

De eerste vorm is een functie verklaring, wat er als volgt uitziet:

function x() {
    console.log('x');
}

Een functieverklaring is een verklaring; het is geen verklaring of uitdrukking. Als zodanig volg je het niet met een ; (hoewel dit niet schadelijk is).

Een functieaangifte wordt verwerkt wanneer uitvoering wordt ingevoerd in de context waarin deze wordt weergegeven, voor elke stapsgewijze code wordt uitgevoerd. De functie die het creëert krijgt een eigen naam (x in het bovenstaande voorbeeld) en die naam wordt geplaatst in de scope waarin de verklaring wordt weergegeven.

Omdat het vóór een stapsgewijze code in dezelfde context wordt verwerkt, kunt u dit soort dingen doen:

x(); // Works even though it's above the declaration
function x() {
    console.log('x');
}

Tot ES2015 dekt de specificatie niet wat een JavaScript-engine zou moeten doen als u een functiedeclaratie plaatst in een controlestructuur zoals try, if, switch, while, etc., zoals dit:

if (someCondition) {
    function foo() {    // <===== HERE THERE
    }                   // <===== BE DRAGONS
}

En omdat ze zijn verwerkt voor stap-voor-stap-code wordt uitgevoerd, het is lastig om te weten wat u moet doen als ze zich in een controlestructuur bevinden.

Hoewel dit niet was gespecificeerd tot ES2015 was het een toegestane uitbreiding om functie-verklaringen in blokken te ondersteunen. Helaas (en onvermijdelijk) deden verschillende engines andere dingen.

Vanaf ES2015 zegt de specificatie wat te doen. In feite geeft het drie afzonderlijke dingen om te doen:

  1. Als in de losse modus niet in een webbrowser zou de JavaScript-engine één ding moeten doen
  2. Als de JavaScript-engine in de losse modus in een webbrowser iets anders doet
  3. Als in streng modus (browser of niet), moet de JavaScript-engine nog een ander ding doen

De regels voor de losse modi zijn lastig, maar in streng modus, functie-declaraties in blokken zijn eenvoudig: ze zijn lokaal in het blok (ze hebben blok bereik, wat ook nieuw is in ES2015) en ze worden naar de bovenkant van het blok gehesen. Zo:

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

"Anoniem" function Uitdrukking

De tweede algemene vorm is een anonieme functie-expressie:

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

Zoals alle uitdrukkingen wordt het geëvalueerd wanneer het wordt bereikt in de stapsgewijze uitvoering van de code.

In ES5 heeft de gecreëerde functie geen naam (deze is anoniem). In ES2015 krijgt de functie zo mogelijk een naam toegewezen door deze uit de context te halen. In het bovenstaande voorbeeld zou de naam zijn y. Iets soortgelijks wordt gedaan wanneer de functie de waarde is van een eigenschap-initializer. (Zoek naar voor meer informatie over wanneer dit gebeurt en de regels SetFunctionName in de de specificatie- het lijkt overal de plaats.)

Genaamd function Uitdrukking

De derde vorm is een benoemde functie-expressie ( "NFE"):

var z = function w() {
    console.log('zw')
};

De functie die hierdoor wordt gecreëerd heeft een eigen naam (w in dit geval). Zoals alle uitdrukkingen wordt dit geëvalueerd wanneer het wordt bereikt in de stapsgewijze uitvoering van de code. De naam van de functie is niet toegevoegd aan de scope waarin de expressie verschijnt; de naam is in scope binnen de functie zelf:

var z = function w() {
    console.log(typeof w); // "function"
};
console.log(typeof w);     // "undefined"

Merk op dat NFE's vaak een bron van bugs zijn geweest voor JavaScript-implementaties. IE8 en eerder, bijvoorbeeld, verwerken NFE's volkomen verkeerd, het creëren van twee verschillende functies op twee verschillende tijdstippen. Vroege versies van Safari hadden ook problemen. Het goede nieuws is dat de huidige versies van browsers (IE9 en hoger, huidige Safari) deze problemen niet meer hebben. (Maar helaas blijft IE8 op dit moment wijdverbreid gebruikt, dus het gebruik van NFE's met code voor het web in het algemeen is nog steeds problematisch.)

Accessor-functie-initialisatie (ES5 +)

Soms kunnen functies grotendeels onopgemerkt sluipen; dat is het geval met accessor-functies. Hier is een voorbeeld:

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"

Merk op dat toen ik de functie gebruikte, ik deze niet gebruikte ()! Dat komt omdat het een is accessor-functie voor een woning. We krijgen en zetten de eigenschap op de normale manier, maar achter de schermen wordt de functie genoemd.

U kunt ook accessofuncties maken met Object.defineProperty, Object.definePropertiesen het minder bekende tweede argument voor Object.create.

Pijlfunctie-expressie (ES2015 +)

ES2015 brengt ons de pijl functie. Hier is een voorbeeld:

var a = [1, 2, 3];
var b = a.map(n => n * 2);
console.log(b.join(", ")); // 2, 4, 6

Zie dat n => n * 2 ding verstopt in de map() bellen? Dat is een functie.

Een paar dingen over pijlfuncties:

  1. Ze hebben die niet this. In plaats daarvan, zij sluit over de this van de context waarin ze zijn gedefinieerd. (Ze sluiten ook over arguments en, waar relevant, super.) Dit betekent dat de this in hen is hetzelfde als de this waar ze zijn gemaakt en niet kunnen worden gewijzigd.

  2. Zoals je hebt gemerkt met het bovenstaande, gebruik je het sleutelwoord niet function; in plaats daarvan gebruik je =>.

De n => n * 2 voorbeeld hierboven is een vorm van hen. Als u meerdere argumenten hebt om de functie door te geven, gebruikt u parens:

var a = [1, 2, 3];
var b = a.map((n, i) => n * i);
console.log(b.join(", ")); // 0, 2, 6

(Onthoudt dat Array#map geeft de invoer als eerste argument en de index als de tweede.)

In beide gevallen is het lichaam van de functie slechts een uitdrukking; de retourwaarde van de functie wordt automatisch het resultaat van die uitdrukking (u gebruikt geen expliciete return).

Als u meer dan alleen een uitdrukking gebruikt, gebruikt u {} en een expliciete return (als u een waarde moet retourneren), zoals normaal:

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

De versie zonder { ... } wordt een pijlfunctie genoemd met een expressie lichaam of beknopte lichaam. (Ook een beknopt pijlfunctie.) Degene met { ... } het definiëren van het lichaam is een pijlfunctie met een functie lichaam. (Ook een breedsprakig pijlfunctie.)

Methodeverklaring in Object Initializer (ES2015 +)

ES2015 staat een kortere vorm toe van het declareren van een eigenschap die verwijst naar een functie; het ziet er zo uit:

var o = {
    foo() {
    }
};

het equivalent in ES5 en eerder zou zijn:

var o = {
    foo: function foo() {
    }
};

Aannemers- en methodenverklaringen in class (ES2015 +)

ES2015 brengt ons class syntaxis, inclusief gedeclareerde constructeurs en methoden:

class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    getFullName() {
        return this.firstName + " " + this.lastName;
    }
}

Er zijn twee functie-verklaringen hierboven: één voor de constructor, die de naam krijgt Person, en een voor getFullName, waaraan een functie is toegewezen Person.prototype.


544
2018-03-04 13:35



Sprekend over de wereldwijde context, beide, de var verklaring en a FunctionDeclaration aan het eind zal een maken non-die kunnen worden gewist eigenschap op het globale object, maar de waarde van beide kan worden overschreven.

Het subtiele verschil tussen de twee manieren is dat wanneer het Variabele Instantiation procesruns (vóór de daadwerkelijke code-uitvoering) alle ID's gedeclareerd met var wordt geïnitialiseerd met undefineden degenen die door de FunctionDeclarationis vanaf dat moment beschikbaar, bijvoorbeeld:

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

De opdracht van de bar  FunctionExpression vindt plaats tot runtime.

Een globale eigenschap gemaakt door een FunctionDeclaration kan zonder problemen worden overschreven, net als een variabele waarde, bijvoorbeeld:

 function test () {}
 test = null;

Een ander voor de hand liggend verschil tussen uw twee voorbeelden is dat de eerste functie geen naam heeft, maar de tweede functie heeft, wat erg handig kan zijn bij het opsporen van fouten (dat wil zeggen het inspecteren van een oproepstack).

Over uw bewerkte eerste voorbeeld (foo = function() { alert('hello!'); };), het is een niet-aangegeven opdracht, ik zou het ten zeerste aanraden om altijd het var trefwoord.

Met een opdracht, zonder de var statement, als de identifier waarnaar wordt verwezen niet wordt gevonden in de scopeketen, wordt dit een die kunnen worden gewist eigendom van het globale object.

Ook gooien niet-gedeclareerde opdrachten een ReferenceError op ECMAScript 5 onder Strikte modus.

A moet lezen:

Notitie: Dit antwoord is samengevoegd uit een andere vraag, waarin de grote twijfel en misvatting van het OP was dat identifiers verklaarden met een FunctionDeclaration, kan niet worden overschreven, wat niet het geval is.


133
2017-08-08 19:32



De twee codefragmenten die je daar hebt geplaatst, zullen zich voor bijna alle doeleinden op dezelfde manier gedragen.

Het verschil in gedrag is echter dat bij de eerste variant (var functionOne = function() {}), kan die functie alleen na dat punt in de code worden opgeroepen.

Met de tweede variant (function functionTwo()), is de functie beschikbaar voor code die wordt uitgevoerd boven waar de functie wordt gedeclareerd.

Dit komt omdat bij de eerste variant de functie is toegewezen aan de variabele foo tijdens runtime. In de tweede wordt de functie toegewezen aan die ID, foo, op parse tijd.

Meer technische informatie

JavaScript heeft drie manieren om functies te definiëren.

  1. Uw eerste fragment toont een functie expressie. Dit houdt in het gebruik van de "functie" -operator om een ​​functie te maken - het resultaat van die operator kan in elke variabele of objecteigenschap worden opgeslagen. De functie-uitdrukking is zo krachtig. De functie-uitdrukking wordt vaak een "anonieme functie" genoemd, omdat deze geen naam hoeft te hebben,
  2. Uw tweede voorbeeld is een functie verklaring. Dit gebruikt de "functie" -instructie om een ​​functie te maken. De functie is beschikbaar tijdens parsen en kan overal in dat bereik worden opgeroepen. U kunt het later nog steeds opslaan in een variabele of objecteigenschap.
  3. De derde manier om een ​​functie te definiëren is de "Functie ()" constructor, wat niet wordt weergegeven in uw oorspronkelijke bericht. Het wordt niet aanbevolen om dit te gebruiken omdat het op dezelfde manier werkt als eval(), wat zijn problemen heeft.

111
2018-04-20 04:54



Een betere uitleg voor Greg's antwoord

functionTwo();
function functionTwo() {
}

Waarom geen fout? We hebben altijd geleerd dat uitdrukkingen van boven naar beneden worden uitgevoerd (??)

Omdat:

Functieverklaringen en variabele aangiften worden altijd verplaatst (hoisted) onzichtbaar bovenaan hun inhoudsbasis door de JavaScript-interpreter. Functieparameters en taalgedefinieerde namen zijn er uiteraard al. ben kers

Dit betekent die code als volgt:

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

Merk op dat het toewijzingsgedeelte van de verklaringen niet werd opgehesen. Alleen de naam wordt gehesen.

Maar in het geval van functieverklaringen, wordt ook het volledige functielichaam gehesen:

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

91
2017-08-09 02:45



Andere commentatoren hebben het semantische verschil van de twee varianten hierboven al behandeld. Ik wilde een stilistisch verschil vaststellen: alleen de "toewijzing" -variatie kan een eigenschap van een ander object instellen.

Ik bouw vaak JavaScript-modules met een patroon als dit:

(function(){
    var exports = {};

    function privateUtil() {
            ...
    }

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

    return exports;
})();

Met dit patroon gebruiken uw openbare functies allemaal toewijzing, terwijl uw persoonlijke functies declaratie gebruiken.

(Houd er ook rekening mee dat voor toewijzing een puntkomma na de instructie nodig is, terwijl de verklaring dit niet toestaat.)


83
2018-03-03 19:19



Een illustratie van wanneer de eerste methode de voorkeur heeft boven de tweede, is wanneer u moet voorkomen dat de vorige definities van een functie worden overschreven.

Met

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

, deze definitie van myfunction zal elke voorgaande definitie overschrijven, omdat deze wordt uitgevoerd in parse-time.

Terwijl

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

doet de juiste definitie myfunction alleen wanneer condition is voldaan.


68
2018-03-29 13:26