Niedawno zacząłem utrzymywać czyjś kod JavaScript. Naprawiam błędy, dodaję funkcje, a także staram się uporządkować kod i uczynić go bardziej spójnym.
Poprzedni programista używa dwóch sposobów deklarowania funkcji i nie wiem, czy jest ku temu powód, czy nie.
Te dwa sposoby to:
var functionOne = function() {
// Some code
};
function functionTwo() {
// Some code
}
Jakie są powody stosowania tych dwóch różnych metod i jakie są wady i zalety każdej z nich? Czy jest coś, co można zrobić za pomocą jednej metody, a czego nie można zrobić za pomocą drugiej?
Różnica polega na tym, że functionOne
jest wyrażeniem funkcji i jest definiowane tylko wtedy, gdy ta linia zostanie osiągnięta, podczas gdy functionTwo
jest deklaracją funkcji i jest definiowane tak szybko, jak otaczająca je funkcja lub skrypt jest wykonywany (z powodu hoisting).
Na przykład, wyrażenie funkcji:
// TypeError: functionOne is not a function
functionOne();
var functionOne = function() {
console.log("Hello!");
};
I deklaracja funkcji:
// Outputs: "Hello!"
functionTwo();
function functionTwo() {
console.log("Hello!");
}
Oznacza to również, że nie można'warunkowo definiować funkcji za pomocą deklaracji funkcji:
if (test) {
// Error or misbehavior
function functionThree() { doSomething(); }
}
Powyższe faktycznie definiuje functionThree
niezależnie od wartości test
's — chyba że use strict
jest w mocy, w którym to przypadku po prostu podnosi błąd.
Najpierw chcę poprawić Greg: function abc(){}
jest scoped too — nazwa abc
jest zdefiniowana w zakresie, w którym napotkano tę definicję. Przykład:
function xyz(){
function abc(){};
// abc is defined here...
}
// ...but not here
Po drugie, możliwe jest połączenie obu stylów:
var xyz = function abc(){};
xyz
będzie zdefiniowany jak zwykle, abc
jest niezdefiniowany we wszystkich przeglądarkach oprócz Internet Explorer—, nie licz na to, że będzie zdefiniowany. Ale będzie on zdefiniowany wewnątrz swojego ciała:
var xyz = function abc(){
// xyz is visible here
// abc is visible here
}
// xyz is visible here
// abc is undefined here
Jeśli chcesz aliasować funkcje na wszystkich przeglądarkach, użyj tego typu deklaracji:
function abc(){};
var xyz = abc;
W tym przypadku zarówno xyz
jak i abc
są aliasami tego samego obiektu:
console.log(xyz === abc); // prints "true"
Jednym z istotnych powodów używania stylu łączonego jest atrybut "name" obiektów funkcyjnych (nie obsługiwany przez Internet Explorer). Zasadniczo, kiedy definiujesz funkcję taką jak
function abc(){};
console.log(abc.name); // prints "abc"
jej nazwa jest automatycznie przypisywana. Ale kiedy zdefiniujesz ją jak
var abc = function(){};
console.log(abc.name); // prints ""
jej nazwa jest pusta — stworzyliśmy anonimową funkcję i przypisaliśmy ją do jakiejś zmiennej.
Innym dobrym powodem do użycia stylu łączonego jest użycie krótkiej nazwy wewnętrznej, aby odnieść się do siebie, jednocześnie zapewniając długą, niekonfliktową nazwę dla użytkowników zewnętrznych:
// 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);
// ...
}
W powyższym przykładzie możemy zrobić to samo z nazwą zewnętrzną, ale będzie to zbyt nieporęczne (i wolniejsze).
(Innym sposobem na odwołanie się do samego siebie jest użycie arguments.callee
, co wciąż jest stosunkowo długie i nie jest obsługiwane w trybie ścisłym.).
W głębi duszy, JavaScript traktuje oba stwierdzenia inaczej. To jest deklaracja funkcji:
function abc(){}
abc
jest tutaj zdefiniowane wszędzie w bieżącym zakresie:
// We can call it here
abc(); // Works
// Yet, it is defined down there.
function abc(){}
// We can call it again
abc(); // Works
Ponadto, jest on podnoszony przez instrukcję return
:
// We can call it here
abc(); // Works
return;
function abc(){}
To jest wyrażenie funkcyjne:
var xyz = function(){};
xyz
jest tu zdefiniowane z punktu przypisania:
// We can't call it here
xyz(); // UNDEFINED!!!
// Now it is defined
xyz = function(){}
// We can call it here
xyz(); // works
Deklaracja funkcji vs wyrażenie funkcji jest prawdziwym powodem, dla którego istnieje różnica zademonstrowana przez Grega.
Zabawny fakt:
var xyz = function abc(){};
console.log(xyz.name); // Prints "abc"
Osobiście wolę deklarację "wyrażenia funkcyjnego", ponieważ w ten sposób mogę kontrolować widoczność. Kiedy definiuję funkcję jak
var abc = function(){};
Wiem, że zdefiniowałem funkcję lokalnie. Kiedy zdefiniuję funkcję jak
abc = function(){};
wiem, że zdefiniowałem ją globalnie, pod warunkiem, że nie zdefiniowałem abc
nigdzie w łańcuchu zakresów. Ten styl definicji jest odporny nawet gdy jest używany wewnątrz eval()
. Podczas gdy definicja
function abc(){};
zależy od kontekstu i może sprawić, że zgadniesz, gdzie jest ona zdefiniowana, szczególnie w przypadku eval()
— odpowiedź brzmi: To zależy od przeglądarki.
W terminologii informatycznej mówimy o funkcjach anonimowych i nazwanych. Myślę, że najważniejszą różnicą jest to, że funkcja anonimowa nie jest związana z nazwą, stąd nazwa funkcja anonimowa. W JavaScript jest to obiekt pierwszej klasy, dynamicznie deklarowany w czasie wykonywania.
Aby uzyskać więcej informacji na temat anonimowych funkcji i rachunku lambda, Wikipedia jest dobrym początkiem (http://en.wikipedia.org/wiki/Anonymous_function).