1. Déclaration de fonction et expression de fonction
Dans ECMAScript, les deux manières les plus courantes de créer des fonctions sont les expressions de fonction et les déclarations de fonction. La différence entre les deux est un peu déroutante, car la spécification ECMA ne précise qu'un seul point : les déclarations de fonction doivent avoir des identifiants (Identifiers). ) (c'est ainsi que tout le monde appelle souvent le nom de la fonction), et cet identifiant peut être omis dans les expressions de fonction :
Déclaration de fonction : nom de la fonction (paramètres : facultatifs){corps de la fonction}
Expression de fonction : nom de la fonction (facultatif) (paramètres : facultatifs) { corps de la fonction }
Donc, on voit que si le nom de la fonction n'est pas déclaré, ce doit être une expression. Mais si le nom de la fonction est déclaré, comment déterminer s'il s'agit d'une déclaration de fonction ou d'une expression de fonction ? ECMAScript se différencie selon le contexte. Si la fonction foo(){} fait partie d'une expression d'affectation, c'est une expression de fonction. Si la fonction foo(){} est contenue dans le corps d'une fonction ou est située en haut du programme. une déclaration de fonction.
function foo(){} // 声明,因为它是程序的一部分 var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分 new function bar(){}; // 表达式,因为它是new表达式 (function(){ function bar(){} // 声明,因为它是函数体的一部分 })();
Il existe une différence très subtile entre les expressions et les déclarations. Tout d'abord, les déclarations de fonction seront analysées et évaluées avant que toute expression ne soit analysée et évaluée. Même si votre déclaration se trouve sur la dernière ligne de code, ce sera la première. L'expression dans la même portée est analysée/évaluée auparavant. Reportez-vous à l'exemple suivant. La fonction fn est déclarée après alert, mais lorsque alert est exécutée, fn est déjà défini :
.alert(fn()); function fn() { return 'Hello world!'; }
De plus, il y a un autre point qui doit être rappelé. Bien que les déclarations de fonction puissent être utilisées dans des instructions conditionnelles, elles n'ont pas été standardisées, ce qui signifie que différents environnements peuvent avoir des résultats d'exécution différents, donc dans ce cas, c'est le cas. il est préférable d'utiliser des expressions de fonction. Formule : Parce qu'il n'y a pas de concept de portée au niveau du bloc dans les instructions conditionnelles
.// 千万别这样做! // 因为有的浏览器会返回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();
Les règles actuelles de déclaration de fonction sont les suivantes :
La déclaration de fonction ne peut apparaître que dans le corps du programme ou de la fonction. Syntaxiquement, ils ne peuvent pas apparaître à l'intérieur d'un bloc ({ … }), par exemple dans une instruction if, while ou for. Parce que Block ne peut contenir que des instructions Statement, mais pas des éléments source tels que des déclarations de fonction. D'un autre côté, un examen plus attentif des règles révèle que la seule façon pour une expression d'apparaître dans un bloc est de faire partie d'une instruction d'expression. Cependant, la spécification indique clairement qu'une instruction d'expression ne peut pas commencer par le mot-clé fonction. Cela signifie en réalité que les expressions de fonction ne peuvent pas apparaître dans les instructions Statement ou les blocs (car les blocs sont composés d'instructions Statement).
2. Expression de fonction nommée
En ce qui concerne les expressions de fonction nommée, bien sûr, elles doivent avoir un nom. L'exemple précédent var bar = function foo(){}; Valable uniquement dans le cadre de la fonction nouvellement définie, car la spécification stipule que les identifiants ne peuvent pas être valides dans le cadre environnant :
var f = function foo(){ return typeof foo; // function --->foo是在内部作用域内有效 }; // foo在外部用于是不可见的 typeof foo; // "undefined" f(); // "function"
Puisque cela est obligatoire, à quoi servent les expressions de fonctions nommées ? Pourquoi un nom ?
Comme nous l'avons dit au début : lui donner un nom peut rendre le processus de débogage plus pratique, car lors du débogage, si chaque élément de la pile d'appels a son propre nom pour le décrire, alors le processus de débogage sera excellent. , le sentiment est différent.
astuces :Voici une petite question : dans ES3, l'objet scope de l'expression de fonction nommée hérite également des propriétés de Object.prototype. Cela signifie que le simple fait de nommer l'expression de fonction amènera également toutes les propriétés d'Object.prototype dans la portée. Les résultats peuvent être surprenants.
var constructor = function(){return null;} var f = function f(){ return construcor(); } f(); //{in ES3 环境}
Ce programme semble produire null, mais il produit en fait un nouvel objet. Parce qu'une expression de fonction nommée hérite de Object.prototype.constructor (c'est-à-dire le constructeur d'Object) dans sa portée. Tout comme l'instruction with, cette portée sera affectée par les modifications dynamiques apportées à Object.prototype. Heureusement, ES5 corrige ce bug.
Une solution raisonnable à ce comportement consiste à créer une variable locale avec le même nom que l'expression de la fonction et à lui attribuer la valeur null. Même dans les environnements qui ne hissent pas incorrectement une déclaration d'expression de fonction, la redéclaration d'une variable avec var garantit que la variable g est toujours liée. Définir la variable g sur null garantit que les fonctions en double peuvent être récupérées.
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的一系列文章。