1. Funktionsdeklaration und Funktionsausdruck
In ECMAScript sind die beiden häufigsten Methoden zum Erstellen von Funktionen Funktionsausdrücke und Funktionsdeklarationen. Der Unterschied zwischen den beiden ist etwas verwirrend, da die ECMA-Spezifikation nur einen Punkt klarstellt: Funktionsdeklarationen müssen Bezeichner (Identifikatoren) haben. ) (wie jeder den Funktionsnamen oft nennt), und dieser Bezeichner kann in Funktionsausdrücken weggelassen werden:
Funktionsdeklaration: Funktionsname (Parameter: optional){Funktionskörper}
Funktionsausdruck: Funktion Funktionsname (optional) (Parameter: optional) { Funktionskörper }
Es ist also ersichtlich, dass es sich um einen Ausdruck handeln muss, wenn der Funktionsname nicht deklariert ist. Wenn der Funktionsname jedoch deklariert ist, wie kann dann festgestellt werden, ob es sich um eine Funktionsdeklaration oder einen Funktionsausdruck handelt? ECMAScript unterscheidet nach Kontext. Wenn die Funktion foo(){} Teil eines Zuweisungsausdrucks ist, handelt es sich um einen Funktionsausdruck. Wenn die Funktion foo(){} in einem Funktionskörper enthalten ist oder sich oben im Programm befindet, ist sie es eine Funktionsdeklaration.
function foo(){} // 声明,因为它是程序的一部分 var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分 new function bar(){}; // 表达式,因为它是new表达式 (function(){ function bar(){} // 声明,因为它是函数体的一部分 })();
Es gibt einen sehr subtilen Unterschied zwischen Ausdrücken und Deklarationen. Zunächst werden Funktionsdeklarationen analysiert und ausgewertet, bevor Ausdrücke analysiert und ausgewertet werden. Auch wenn Ihre Deklaration in der letzten Codezeile steht Ausdruck im gleichen Bereich wird zuvor analysiert/ausgewertet. Siehe das folgende Beispiel. Die Funktion fn wird nach der Warnung deklariert, aber wenn die Warnung ausgeführt wird, ist fn bereits definiert:
alert(fn()); function fn() { return 'Hello world!'; }
Darüber hinaus muss an einen weiteren Punkt erinnert werden, obwohl Funktionsdeklarationen in bedingten Anweisungen verwendet werden können. Dies bedeutet, dass unterschiedliche Umgebungen unterschiedliche Ausführungsergebnisse haben können, was in diesem Fall der Fall ist Am besten verwenden Sie Funktionsausdrücke: Weil es in bedingten Anweisungen kein Konzept für den Gültigkeitsbereich auf Blockebene gibt
// 千万别这样做! // 因为有的浏览器会返回first的这个function,而有的浏览器返回的却是第二个 if (true) { function foo() { return 'first'; } } else { function foo() { return 'second'; } } foo(); // 相反,这样情况,我们要用函数表达式 var foo; if (true) { foo = function() { return 'first'; }; } else { foo = function() { return 'second'; }; } foo();
Funktionsdeklarationen können nur im Programm- oder Funktionskörper erscheinen. Syntaktisch gesehen können sie nicht innerhalb eines Blocks ({ … }) erscheinen, beispielsweise innerhalb einer if-, while- oder for-Anweisung. Denn Block kann nur Statement-Anweisungen enthalten, nicht aber Quellelemente wie Funktionsdeklarationen. Andererseits zeigt ein genauerer Blick auf die Regeln, dass ein Ausdruck nur dann in einem Block erscheinen kann, wenn er Teil einer Ausdrucksanweisung ist. In der Spezifikation heißt es jedoch eindeutig, dass eine Ausdrucksanweisung nicht mit dem Schlüsselwort function beginnen darf. Das bedeutet eigentlich, dass Funktionsausdrücke nicht in Statement-Anweisungen oder Blöcken vorkommen können (da Blöcke aus Statement-Anweisungen bestehen).
2. Benannter Funktionsausdruck
Wenn es um benannte Funktionsausdrücke geht, muss dieser natürlich einen Namen haben. Das vorherige Beispiel var bar = function foo(){} ist ein gültiger benannter Funktionsausdruck, aber es gibt eine Sache, die man sich merken sollte: diesen Namen Nur im Rahmen der neu definierten Funktion gültig, da die Spezifikation vorsieht, dass Bezeichner im umgebenden Bereich nicht gültig sein können:
var f = function foo(){ return typeof foo; // function --->foo是在内部作用域内有效 }; // foo在外部用于是不可见的 typeof foo; // "undefined" f(); // "function"
Wie wir zu Beginn gesagt haben: Wenn Sie ihm einen Namen geben, kann dies den Debugging-Prozess komfortabler machen, denn wenn beim Debuggen jedes Element im Aufrufstapel einen eigenen Namen hat, um es zu beschreiben, ist der Debugging-Prozess großartig , das Gefühl ist anders.
Tipps:Hier eine kleine Frage: In ES3 erbt das Bereichsobjekt des benannten Funktionsausdrucks auch die Eigenschaften von Object.prototype. Das bedeutet, dass durch die einfache Benennung des Funktionsausdrucks auch alle Eigenschaften von Object.prototype in den Gültigkeitsbereich einbezogen werden. Die Ergebnisse mögen überraschend sein.
var constructor = function(){return null;} var f = function f(){ return construcor(); } f(); //{in ES3 环境}
Eine sinnvolle Lösung für dieses Verhalten besteht darin, eine lokale Variable mit demselben Namen wie der Funktionsausdruck zu erstellen und ihr den Wert Null zuzuweisen. Selbst in Umgebungen, in denen die Deklaration eines Funktionsausdrucks nicht fälschlicherweise erfolgt, stellt die erneute Deklaration einer Variablen mit var sicher, dass die Variable g weiterhin gebunden ist. Wenn Sie die Variable g auf Null setzen, wird sichergestellt, dass doppelte Funktionen durch Garbage Collection erfasst werden können.
var f = function g(){ return 17; } var g =null;
3、调试器(调用栈)中的命名函数表达式
刚才说了,命名函数表达式的真正用处是调试,那到底怎么用呢?如果一个函数有名字,那调试器在调试的时候会将它的名字显示在调用的栈上。有些调试器(Firebug)有时候还会为你们函数取名并显示,让他们和那些应用该函数的便利具有相同的角色,可是通常情况下,这些调试器只安装简单的规则来取名,所以说没有太大价值,我们来看一个例子:不用命名函数表达式
function foo(){ return bar(); } function bar(){ return baz(); } function baz(){ debugger; } foo(); // 这里我们使用了3个带名字的函数声明 // 所以当调试器走到debugger语句的时候,Firebug的调用栈上看起来非常清晰明了 // 因为很明白地显示了名称 baz bar foo expr_test.html()
通过查看调用栈的信息,我们可以很明了地知道foo调用了bar, bar又调用了baz(而foo本身有在expr_test.html文档的全局作用域内被调用),不过,还有一个比较爽地方,就是刚才说的Firebug为匿名表达式取名的功能:
function foo(){ return bar(); } var bar = function(){ return baz(); } function baz(){ debugger; } foo(); // Call stack baz bar() //看到了么? foo expr_test.html()
然后,当函数表达式稍微复杂一些的时候,调试器就不那么聪明了,我们只能在调用栈中看到问号:
function foo(){ return bar(); } var bar = (function(){ if (window.addEventListener) { return function(){ return baz(); }; } else if (window.attachEvent) { return function() { return baz(); }; } })(); function baz(){ debugger; } foo(); // Call stack baz (?)() // 这里可是问号哦,显示为匿名函数(anonymous function) foo expr_test.html()
另外,当把函数赋值给多个变量的时候,也会出现令人郁闷的问题:
function foo(){ return baz(); } var bar = function(){ debugger; }; var baz = bar; bar = function() { alert('spoofed'); }; foo(); // Call stack: bar() foo expr_test.html()
这时候,调用栈显示的是foo调用了bar,但实际上并非如此,之所以有这种问题,是因为baz和另外一个包含alert(‘spoofed')的函数做了引用交换所导致的。
归根结底,只有给函数表达式取个名字,才是最委托的办法,也就是使用命名函数表达式。我们来使用带名字的表达式来重写上面的例子(注意立即调用的表达式块里返回的2个函数的名字都是bar):
function foo(){ return bar(); } var bar = (function(){ if (window.addEventListener) { return function bar(){ return baz(); }; } else if (window.attachEvent) { return function bar() { return baz(); }; } })(); function baz(){ debugger; } foo(); // 又再次看到了清晰的调用栈信息了耶! baz bar foo expr_test.html()
好的,整个文章结束,大家对javascript的认识又近了一步,希望大家越来越喜欢小编为大家整理的文章,继续关注跟我学习javascript的一系列文章。