JavaScript-funktionsordning: varför spelar det roll?


Ursprunglig fråga:

JSHint klagar när min JavaScript anropar en funktion som är definierad längre ner på sidan än anropet till den. Men min sida är för ett spel, och inga funktioner anropas förrän hela sidan har laddats. Så varför spelar ordningen som funktionerna visas i min kod någon roll?

REDIGERA: Jag tror att jag kan ha hittat svaret.

http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting

Jag stönar inombords. Det ser ut som att jag behöver tillbringa ÄNNU en dag med att omordna sex tusen rader kod. Inlärningskurvan med JavaScript är inte alls brant, men den är väldigt lååång.

Svar #1

tl;dr Om du inte anropar något förrän allt har laddats, borde du vara okej.

Redigera: För en översikt som även täcker några ES6-deklarationer (let, const): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Scope_Cheatsheet

Detta konstiga beteende beror på

  1. Hur du definierar funktionerna och
  2. När du ringer dem.

Här är några exempel.

bar(); //This won't throw an error
function bar() {}

foo(); //This will throw an error
var foo = function() {}
bar();
function bar() {
    foo(); //This will throw an error
}
var foo = function() {}
bar();
function bar() {
    foo(); //This _won't_ throw an error
}
function foo() {}
function bar() {
    foo(); //no error
}
var foo = function() {}
bar();

Detta beror på något som kallas hoisting!

Det finns två sätt att definiera funktioner: Funktionsdeklaration och funktionsuttryck. Skillnaden är irriterande och liten, så låt oss bara säga denna något felaktiga sak: Om du skriver det som function name() {}, är det en deklaration, och när du skriver det som var name=function() {} (eller en anonym funktion tilldelad ett returvärde, sådana saker), är det ett funktionsuttryck.

Först, låt oss titta på hur variabler hanteras:

var foo = 42;

//the interpreter turns it into this:
var foo;
foo = 42;

Nu, hur deklarationer av funktioner hanteras:

var foo = 42;
function bar() {}

//turns into
var foo; //Insanity! It's now at the top
function bar() {}
foo = 42;

Uttalandena var "kastar" skapandet av foo till allra toppen, men tilldelar inte värdet till det än. Funktionsdeklarationen kommer näst i ordningen, och slutligen tilldelas ett värde till foo.

Och vad sägs om detta?

bar();
var foo = 42;
function bar() {}
//=>
var foo;
function bar() {}
bar();
foo = 42;

Endast deklarationen av foo flyttas till toppen. Tilldelningen sker först efter att anropet till bar har gjorts, där det var innan all hoisting inträffade.

Och slutligen, för korthetens skull:

bar();
function bar() {}
//turns to
function bar() {}
bar();

Nu, vad sägs om funktionsuttryck?

var foo = function() {}
foo();
//=>
var foo;
foo = function() {}
foo();

Precis som vanliga variabler deklareras först foo på den högsta punkten i scopet, sedan tilldelas det ett värde.

Låt oss se varför det andra exemplet ger ett fel.

bar();
function bar() {
    foo();
}
var foo = function() {}
//=>
var foo;
function bar() {
    foo();
}
bar();
foo = function() {}

Som vi har sett tidigare, är det endast skapandet av foo som flyttas upp, tilldelningen sker där den dök upp i den "ursprungliga" (icke-flyttade) koden. När bar anropas är det innan foo har tilldelats ett värde, så foo === undefined. Nu, i funktionskroppen för bar, är det som om du gör undefined(), vilket kastar ett fel.

Svar #2

Huvudorsaken är förmodligen att JSLint endast gör ett genomgång av filen, så det vet inte att du kommer att definiera en sådan funktion.

Om du använde funktionsuttalande-syntax

function foo(){ ... }

Det finns faktiskt ingen skillnad alls var du deklarerar funktionen (det beter sig alltid som om deklarationen är i början).

Å andra sidan, om din funktion var inställd som en vanlig variabel

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

Du måste garantera att du inte kallar det före initialiseringen (detta kan faktiskt vara en källa till buggar).

Eftersom det är komplicerat att omordna massor av kod och kan i sig själv vara en källa till buggar, skulle jag föreslå att du söker efter en kringgående lösning. Jag är ganska säker på att du kan tala om för JSLint namnet på globala variabler i förväg så att det inte klagar på odeklarerade saker.

Sätt en kommentar i början av filen

/*globals foo1 foo2 foo3*/

Eller så kan du använda en textruta där för det. (Jag tror också att du kan skicka detta i argumenten till den inre jslint-funktionen om du kan pilla med den.)

Relaterade artiklar