我最近开始维护别人的JavaScript代码。我正在修复bug,添加特性,并试图整理代码,使其更加一致。
前面的开发人员使用了两种声明函数的方法,我不知道这背后是否有原因。
这两种方法是:
1 2 3 | var functionOne = function() { // Some code }; |
1 2 3 | function functionTwo() { // Some code } |
使用这两种不同方法的原因是什么?每种方法的优缺点是什么?有没有一种方法可以用另一种方法做不到的?
不同之处在于
例如,一个函数表达式:
1 2 3 4 5 6 | // TypeError: functionOne is not a function functionOne(); var functionOne = function() { console.log("Hello!"); }; |
函数声明:
1 2 3 4 5 6 | // Outputs:"Hello!" functionTwo(); function functionTwo() { console.log("Hello!"); } |
这也意味着您不能使用函数声明有条件地定义函数:
1 2 3 4 | if (test) { // Error or misbehavior function functionThree() { doSomething(); } } |
上面实际上定义了
首先我想纠正格雷格:
1 2 3 4 5 | function xyz(){ function abc(){}; // abc is defined here... } // ...but not here |
其次,这两种风格可以结合:
1 | var xyz = function abc(){}; |
1 2 3 4 5 6 | var xyz = function abc(){ // xyz is visible here // abc is visible here } // xyz is visible here // abc is undefined here |
如果要在所有浏览器上别名函数,请使用以下声明:
1 2 | function abc(){}; var xyz = abc; |
在这种情况下,
1 | console.log(xyz === abc); // prints"true" |
使用组合样式的一个重要原因是函数对象的"name"属性(internet&; Explorer不支持)。基本上当你定义一个函数时
1 2 | function abc(){}; console.log(abc.name); // prints"abc" |
它的名称是自动分配的。但是当你这样定义它的时候
1 2 | var abc = function(){}; console.log(abc.name); // prints"" |
它的名字是空的—我们创建了一个匿名函数,并将其分配给某个变量。
使用组合样式的另一个好理由是使用一个简短的内部名称来引用它自己,同时为外部用户提供一个不冲突的长名称:
1 2 3 4 5 6 7 8 9 | // 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); // ... } |
在上面的示例中,我们可以对外部名称执行相同的操作,但是它太笨重(而且速度更慢)。
(另一种引用自身的方法是使用
实际上,JavaScript对这两个语句的处理是不同的。这是一个函数声明:
1 | function abc(){} |
1 2 3 4 5 6 7 8 | // We can call it here abc(); // Works // Yet, it is defined down there. function abc(){} // We can call it again abc(); // Works |
此外,委员会通过了一项
1 2 3 4 | // We can call it here abc(); // Works return; function abc(){} |
这是一个函数表达式:
1 | var xyz = function(){}; |
1 2 3 4 5 6 7 8 | // We can't call it here xyz(); // UNDEFINED!!! // Now it is defined xyz = function(){} // We can call it here xyz(); // works |
函数声明与函数表达式是Greg所演示的不同之处的真正原因。
有趣的事实:
1 2 | var xyz = function abc(){}; console.log(xyz.name); // Prints"abc" |
就我个人而言,我更喜欢"函数表达式"声明,因为这样我就可以控制可见性。当我像这样定义函数时
1 | var abc = function(){}; |
我知道我在局部定义了这个函数。当我像这样定义函数时
1 | abc = function(){}; |
我知道我在范围链的任何地方都没有定义
1 | function abc(){}; |
取决于上下文,可能会让您猜测它实际上是在哪里定义的,特别是在
下面是创建函数的标准表单的概要:(最初是为另一个问题编写的,但在转入规范问题后进行了调整)。
术语:
ES5: ECMAScript第五版,2009ES2015: ECMAScript 2015(也称为"ES6")快速列表:
函数声明
"匿名"
名为
访问器函数初始化器(ES5+)
箭头函数表达式(ES2015+)(与匿名函数表达式一样,它不涉及显式名称,但可以使用名称创建函数)
对象初始化器中的方法声明(ES2015+)
函数声明
第一个表单是一个函数声明,它看起来像这样:
1 2 3 | function x() { console.log('x'); } |
函数声明就是声明;它不是一个语句或表达式。因此,您不会在后面加上
函数声明是在执行进入它所出现的上下文时处理的,然后才执行任何一步一步的代码。它创建的函数被赋予一个适当的名称(在上面的例子中是
因为在相同的上下文中,它是在任何一步一步的代码之前处理的,所以您可以这样做:
1 2 3 4 | x(); // Works even though it's above the declaration function x() { console.log('x'); } |
直到2015年,规范才涵盖了JavaScript引擎应该做什么,如果你把一个函数声明放在一个控件结构里面,比如
1 2 3 4 | if (someCondition) { function foo() { // <===== HERE THERE } // <===== BE DRAGONS } |
而且,由于它们是在分步代码运行之前处理的,所以很难知道在控件结构中应该做什么。
虽然直到2015年才指定这样做,但是它是一个允许的扩展,可以支持块中的函数声明。不幸的是(也是不可避免的),不同的引擎做不同的事情。
从ES2015开始,规范说明了应该做什么。事实上,它提供了三个单独的任务:
如果不是在web浏览器上,而是在松散模式下,JavaScript引擎应该做一件事如果在web浏览器上处于松散模式,JavaScript引擎应该执行其他操作如果在严格模式下(无论是否在浏览器中),JavaScript引擎应该执行另一项任务松散模式的规则很复杂,但在严格模式下,块中的函数声明很简单:它们是块的局部(它们具有块范围,这在ES2015中也是新的),并且它们被提升到块的顶部。所以:
1 2 3 4 5 6 7 8 | "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) |
"匿名"
第二种常见形式称为匿名函数表达式:
1 2 3 | var y = function () { console.log('y'); }; |
与所有表达式一样,在逐步执行代码时,它将被计算。
在ES5中,它创建的函数没有名称(它是匿名的)。在ES2015中,如果可能的话,可以通过上下文推断来为函数分配一个名称。在上面的例子中,名称应该是
叫function 表达式
第三种形式是命名函数表达式("NFE"):
1 2 3 | var z = function w() { console.log('zw') }; |
它创建的函数有一个专有名称(在本例中为
1 2 3 4 | var z = function w() { console.log(typeof w); //"function" }; console.log(typeof w); //"undefined" |
注意,nfe经常是JavaScript实现的bug来源。例如,IE8和更早版本完全错误地处理了NFEs,在两个不同的时间创建了两个不同的函数。Safari的早期版本也有问题。好消息是,当前版本的浏览器(IE9及以上版本,当前Safari)不再存在这些问题。(遗憾的是,在撰写本文时,IE8仍在广泛使用,因此在web代码中使用NFEs通常仍然存在问题。)
访问器函数初始化器(ES5+)有时,功能可能在很大程度上悄无声息地进入;这就是访问器函数的情况。这里有一个例子:
1 2 3 4 5 6 7 8 9 10 11 | 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" |
注意,当我使用这个函数时,我没有使用
您还可以使用
ES2015给我们带来了arrow函数。这里有一个例子:
1 2 3 | var a = [1, 2, 3]; var b = a.map(n => n * 2); console.log(b.join(",")); // 2, 4, 6 |
看到隐藏在
关于箭头函数有几点:
它们没有自己的
正如您在上面所注意到的,您没有使用关键字
上面的
1 2 3 | var a = [1, 2, 3]; var b = a.map((n, i) => n * i); console.log(b.join(",")); // 0, 2, 6 |
(请记住
在这两种情况下,函数体只是一个表达式;函数的返回值将自动成为该表达式的结果(您不使用显式的
如果你做的不仅仅是一个表达式,使用
说到全局上下文,
这两种方法的细微差别在于,当变量实例化过程运行时(在实际代码执行之前),所有用
1 2 3 4 5 | alert(typeof foo); // 'function', it's already available alert(typeof bar); // 'undefined' function foo () {} var bar = function () {}; alert(typeof bar); // 'function' |
1 2 | function test () {} test = null; |
您的两个示例之间的另一个明显区别是,第一个函数没有名称,但是第二个函数有名称,这在调试(即检查调用堆栈)时非常有用。
关于您编辑的第一个示例(
如果使用赋值,而不使用
此外,未声明的赋值会在严格模式下对ECMAScript 5抛出一个
一个必须阅读:
命名函数表达式被解密注意:这个答案与另一个问题合并了,在这个问题中,OP的主要疑问和误解是,用
您在这里发布的两个代码片段几乎在所有目的上都具有相同的行为。
但是,行为上的不同之处在于,对于第一个变量(
对于第二个变体(
这是因为对于第一个变量,函数在运行时被分配给变量
更多的技术信息
JavaScript有三种定义函数的方法。
第一个代码片段显示了一个函数表达式。这涉及到使用"函数"操作符创建函数——该操作符的结果可以存储在任何变量或对象属性中。函数表达式就是这样强大的。函数表达式通常称为"匿名函数",因为它不必有名称,第二个例子是函数声明。它使用"function"语句来创建一个函数。函数在解析时可用,并且可以在该作用域中的任何位置调用。稍后您仍然可以将其存储在变量或对象属性中。定义函数的第三种方法是"function()"构造函数,这在您最初的文章中没有显示。不建议使用它,因为它的工作方式与对格雷格的回答有更好的解释
1 2 3 | functionTwo(); function functionTwo() { } |
为什么没有错误?我们总是被教导表达式是从上到下执行的(??)
因为:
Function declarations and variable declarations are always moved (
hoisted ) invisibly to the top of their containing scope by the JavaScript interpreter. Function parameters and language-defined names are, obviously, already there. ben cherry
这意味着这样的代码:
1 2 3 4 5 | functionOne(); --------------- var functionOne; | is actually | functionOne(); var functionOne = function(){ | interpreted |--> }; | like | functionOne = function(){ --------------- }; |
请注意,声明书的转让部分并没有悬挂。只有名字被悬挂。
但在功能声明的情况下,整个功能体也会被悬挂:
1 2 3 4 5 | functionTwo(); --------------- function functionTwo() { | is actually | }; function functionTwo() { | interpreted |--> } | like | functionTwo(); --------------- |
其他评论者已经讨论了上述两种变体的语义差异。我想指出一个风格上的差异:只有"赋值"变体可以设置另一个对象的属性。
我经常用这样的模式构建JavaScript模块:
1 2 3 4 5 6 7 8 9 10 11 12 13 | (function(){ var exports = {}; function privateUtil() { ... } exports.publicUtil = function() { ... }; return exports; })(); |
使用这种模式,您的公共函数将全部使用赋值,而您的私有函数将使用声明。
(还要注意,赋值应该在语句后面使用分号,而声明禁止使用分号。)
当您需要避免覆盖函数以前的定义时,就说明了什么时候应该选择第一种方法而不是第二种方法。
与
1 2 3 4 5 | if (condition){ function myfunction(){ // Some code } } |
,
而
1 2 3 4 5 | if (condition){ var myfunction = function (){ // Some code } } |
仅当遇到
一个重要的原因是添加一个且只有一个变量作为命名空间的"根"…
1 2 3 4 | var MyNamespace = {} MyNamespace.foo= function() { } |
或
1 2 3 4 5 | var MyNamespace = { foo: function() { }, ... } |
命名空间有许多技术。随着可用的JavaScript模块越来越多,这一点变得越来越重要。
还请参见如何在JavaScript中声明名称空间?
提升是JavaScript解释器将所有变量和函数声明移动到当前作用域顶部的操作。
然而,只有实际申报才会被悬挂。把任务留在原地。
变量/函数在页面内声明的是全局的,可以访问该页面中的任何位置。函数内部声明的变量/函数具有局部作用域。表示它们在函数体(作用域)内可用/可访问,而在函数体之外不可用。变量
Javascript称为松散类型语言。这意味着Javascript变量可以保存任何数据类型的值。Javascript自动根据运行时提供的值/文字来更改变量类型。
1 2 3 4 5 6 7 8 9 10 | global_Page = 10; var global_Page; ? undefined ? Integer literal, Number Type. ------------------- global_Page = 10; ? Number global_Page = 'Yash'; | Interpreted | global_Page = 'Yash'; ? String ? String literal, String Type. ? AS ? global_Page = true; ? Boolean var global_Page = true; | | global_Page = function (){ ? function ? Boolean Type ------------------- var local_functionblock; ? undefined global_Page = function (){ local_functionblock = 777;? Number var local_functionblock = 777; }; // Assigning function as a data. }; |
函数
1 2 3 4 5 6 | function Identifier_opt ( FormalParameterList_opt ) { FunctionBody | sequence of statements ? return; Default undefined ? return 'some data'; } |
在页面内部声明的函数被悬挂到具有全局访问权限的页面顶部。在功能块内声明的功能被悬挂到该功能块的顶部。
函数默认返回值为"undefined",变量声明默认值也为"undefined"
1 2 | Scope with respect to function-block global. Scope with respect to page undefined | not available. |
函数声明
1 2 3 4 5 6 7 8 9 | function globalAccess() { function globalAccess() { } ------------------- } globalAccess(); | | function globalAccess() { ? Re-Defined / overridden. localAccess(); ? Hoisted As ? function localAccess() { function globalAccess() { | | } localAccess(); ------------------- localAccess(); ? function accessed with in globalAccess() only. function localAccess() { } } globalAccess(); } localAccess(); ? ReferenceError as the function is not defined |
函数表达式
1 2 3 4 5 6 7 8 9 10 11 12 | 10; ? literal (10); ? Expression (10).toString() -> '10' var a; a = 10; ? Expression var a.toString() -> '10' (function invoke() { ? Expression Function console.log('Self Invoking'); (function () { }); }) () -> 'Self Invoking' var f; f = function (){ ? Expression var Function console.log('var Function'); f () -> 'var Function' }; |
函数赋给变量的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | (function selfExecuting(){ console.log('IIFE - Immediately-Invoked Function Expression'); }()); var anonymous = function (){ console.log('anonymous function Expression'); }; var namedExpression = function for_InternalUSE(fact){ if(fact === 1){ return 1; } var localExpression = function(){ console.log('Local to the parent Function Scope'); }; globalExpression = function(){ console.log('creates a new global variable, then assigned this function.'); }; //return; //undefined. return fact * for_InternalUSE( fact - 1); }; namedExpression(); globalExpression(); |
javascript解释为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | var anonymous; var namedExpression; var globalExpression; anonymous = function (){ console.log('anonymous function Expression'); }; namedExpression = function for_InternalUSE(fact){ var localExpression; if(fact === 1){ return 1; } localExpression = function(){ console.log('Local to the parent Function Scope'); }; globalExpression = function(){ console.log('creates a new global variable, then assigned this function.'); }; return fact * for_InternalUSE( fact - 1); // DEFAULT UNDEFINED. }; namedExpression(10); globalExpression(); |
您可以使用
构造函数类:使用Function.prototype.bind创建的函数对象
JavaScript将函数视为一类对象,因此作为对象,您可以为函数分配属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | function Shape(id) { // Function Declaration this.id = id; }; // Adding a prototyped method to a function. Shape.prototype.getID = function () { return this.id; }; Shape.prototype.setID = function ( id ) { this.id = id; }; var expFn = Shape; // Function Expression var funObj = new Shape( ); // Function Object funObj.hasOwnProperty('prototype'); // false funObj.setID( 10 ); console.log( funObj.getID() ); // 10 |
ES6引入的Arrow函数:Arrow函数表达式语法更短,最适合非方法函数,不能用作构造函数。
ArrowFunction : ArrowParameters => ConciseBody .
1
2
3 const fn = (item) => { return item & 1 ? 'Odd' : 'Even'; };
console.log( fn(2) ); // Even
console.log( fn(3) ); // Odd
我加上我自己的答案,只是因为其他人已经完全涵盖了提升部分。
很长一段时间以来,我一直在思考哪种方式更好,多亏了http://jsperf.com,现在我知道了:)
函数声明更快,这才是web开发中真正重要的,对吗?,)
一旦绑定建立,分配给变量的函数声明和函数表达式的行为就会相同。
但是,在如何以及何时将函数对象与其变量关联上存在差异。这种差异是由于JavaScript中称为变量提升的机制造成的。
基本上,所有函数声明和变量声明都被提升到声明发生的函数的顶部(这就是为什么我们说JavaScript具有函数作用域)。
当悬挂功能声明时,功能体"如下"所以当函数体被求值时,变量会立刻绑定到函数对象。
当挂起变量声明时,初始化不会挂起跟在后面,却"落在后面"。变量初始化为
提升的顺序也很重要:函数声明优先于同名的变量声明,最后一个函数声明优先于同名的前一个函数声明。
一些例子…
1 2 3 4 5 6 | var foo = 1; function bar() { if (!foo) { var foo = 10 } return foo; } bar() // 10 |
变量
1 2 3 4 5 6 7 8 9 10 11 12 13 | function f() { return a; function a() {return 1}; var a = 4; function a() {return 2}} f()() // 2 function f() { return a; var a = 4; function a() {return 1}; function a() {return 2}} f()() // 2 |
函数声明优先于变量声明,最后一个函数声明"坚持"。
1 2 3 4 5 6 | function f() { var a = 4; function a() {return 1}; function a() {return 2}; return a; } f() // 4 |
在这个例子中,
1 2 3 4 5 6 7 | var a = 1; function b() { a = 10; return; function a() {}} b(); a // 1 |
这里首先挂起函数声明,声明并初始化变量
第一个例子是函数声明:
1 | function abc(){} |
第二个例子是函数表达式:
1 | var abc = function() {}; |
主要的不同之处在于它们是如何悬挂(悬挂和声明)的。在第一个例子中,整个函数声明被挂起。在第二个例子中,只有var 'abc'被挂起,它的值(函数)将是未定义的,函数本身保持在声明它的位置。
简单地说:
1 2 3 4 5 6 7 | //this will work abc(param); function abc(){} //this would fail abc(param); var abc = function() {} |
为了更好地研究这个主题,我强烈建议您这样做链接
在代码维护成本方面,命名函数更可取:
独立于声明它们的地方(但仍然受到范围的限制)。更能抵抗条件初始化之类的错误(如果需要,仍然可以覆盖)。通过将局部函数与范围功能分开分配,代码的可读性更强。通常在作用域中,功能放在前面,然后声明本地函数。在调试器中,您将清楚地看到调用堆栈上的函数名,而不是"匿名/评估"函数。我怀疑下面会有更多支持指定函数的人。对于匿名函数来说,命名函数的优点是缺点。
从历史上看,匿名函数出现的原因是JavaScript作为一种语言无法列出具有指定函数的成员:
1 2 3 4 | { member:function() { /* How do I make"this.member" a named function? */ } } |
我在代码中使用变量方法有一个非常特殊的原因,上面以抽象的方式介绍了变量方法的理论,但是一个例子可能会帮助一些像我这样的JavaScript专家。
我的代码需要运行160个独立设计的品牌。大多数代码都在共享文件中,但是特定于品牌的内容在单独的文件中,每个品牌一个。
有些品牌需要特定的功能,有些则不需要。有时我不得不添加新的函数来做新的特定于品牌的事情。我很乐意更改共享代码,但我不想更改所有160组品牌文件。
通过使用变量语法,我可以在共享代码中声明变量(本质上是函数指针),并分配一个简单的存根函数,或者将其设置为null。
需要函数的特定实现的一两个标记可以定义函数的版本并将其分配给变量(如果需要的话),其余的什么也不做。在在共享代码中执行null函数之前,我可以测试它。
从上面人们的评论中,我认为也可以重新定义一个静态函数,但是我认为变量解很好,也很清晰。
在计算机科学术语中,我们讨论匿名函数和命名函数。我认为最重要的区别是一个匿名函数没有绑定到一个名称,因此命名为匿名函数。在JavaScript中,它是在运行时动态声明的第一个类对象。
关于匿名函数和lambda演算的更多信息,Wikipedia是一个很好的开始(http://en.wikipedia.org/wiki/Anonymous_function)。
格雷格的回答很好,但我还是想补充一些我刚刚在道格拉斯·克罗克福德的视频中学到的东西。
函数表达式:
1 | var foo = function foo() {}; |
函数声明:
1 | function foo() {}; |
函数语句只是一个带有
所以
1 | function foo() {}; |
扩大到
1 | var foo = function foo() {}; |
进一步扩展到:
1 2 | var foo = undefined; foo = function foo() {}; |
它们都被吊到代码的顶端。
@EugeneLazutkin给出了一个例子,他将一个指定的函数命名为能够使用
教程中的示例:
测试失败时,原始忍者对象被删除。(13页)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | var ninja = { yell: function(n){ return n > 0 ? ninja.yell(n-1) +"a" :"hiy"; } }; assert( ninja.yell(4) =="hiyaaaa","A single object isn't too bad, either." ); var samurai = { yell: ninja.yell }; var ninja = null; try { samurai.yell(4); } catch(e){ assert( false,"Uh, this isn't good! Where'd ninja.yell go?" ); } |
如果您为递归调用的函数命名,则测试将通过。(14页)
1 2 3 4 5 6 7 8 9 10 | var ninja = { yell: function yell(n){ return n > 0 ? yell(n-1) +"a" :"hiy"; } }; assert( ninja.yell(4) =="hiyaaaa","Works as we would expect it to!" ); var samurai = { yell: ninja.yell }; var ninja = {}; assert( samurai.yell(4) =="hiyaaaa","The method correctly calls itself." ); |
其他答案中没有提到的另一个区别是,如果使用匿名函数
1 2 3 | var functionOne = function() { // Some code }; |
用它作为构造函数
1 | var one = new functionOne(); |
那么
与
1 2 3 4 | function functionTwo() { // Some code } two = new functionTwo(); |
可以使用
第一个(函数doSomething(x))应该是对象符号的一部分。
第二个(
您可能想知道什么是函数声明和函数表达式。
函数声明定义了一个命名函数变量,而不需要赋值变量。函数声明作为独立构造出现,不能嵌套在非函数块中。
1 2 3 | function foo() { return 3; } |
ECMA 5 (13.0) defines the syntax as
function Identifier ( FormalParameterListopt ) { FunctionBody }
在上述条件下,函数名在其作用域和其父作用域内都是可见的(否则将无法访问)。
在函数表达式中
函数表达式将函数定义为较大表达式语法(通常是变量赋值)的一部分。通过函数表达式定义的函数可以命名或匿名。函数表达式不应该以"Function"开头。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // Anonymous function expression var a = function() { return 3; } // Named function expression var a = function foo() { return 3; } // Self-invoking function expression (function foo() { alert("hello!"); })(); |
ECMA 5 (13.0) defines the syntax as
function Identifieropt ( FormalParameterListopt ) { FunctionBody }
如果你使用这些函数来创建对象,你会得到:
1 2 3 4 5 | var objectOne = new functionOne(); console.log(objectOne.__proto__); // prints"Object {}" because constructor is an anonymous function var objectTwo = new functionTwo(); console.log(objectTwo.__proto__); // prints"functionTwo {}" because constructor is a named function |
我列出的差异如下:
函数声明可以放在代码中的任何位置。即使它是在定义出现在代码中之前调用的,它也是在函数声明提交到内存中或以一种挂起的方式执行的,在页面中的任何其他代码开始执行之前。
请看下面的函数:
1 2 3 4 5 6 7 8 9 10 | function outerFunction() { function foo() { return 1; } return foo(); function foo() { return 2; } } alert(outerFunction()); // Displays 2 |
这是因为,在执行过程中,它看起来像:-
1 2 3 4 5 6 7 8 9 10 11 | function foo() { // The first function declaration is moved to top return 1; } function foo() { // The second function declaration is moved to top return 2; } function outerFunction() { return foo(); } alert(outerFunction()); //So executing from top to bottom, //the last foo() returns 2 which gets displayed |
函数表达式如果在调用之前没有定义,将导致错误。此外,这里函数定义本身不像函数声明那样移动到顶部或提交到内存中。但是我们赋值给函数的变量会上升未定义的变量会被赋值给它。
使用函数表达式的相同函数:
1 2 3 4 5 6 7 8 9 10 | function outerFunction() { var foo = function() { return 1; } return foo(); var foo = function() { return 2; } } alert(outerFunction()); // Displays 1 |
这是因为在执行过程中,它看起来像:
1 2 3 4 5 6 7 8 9 10 11 12 13 | function outerFunction() { var foo = undefined; var foo = undefined; foo = function() { return 1; }; return foo (); foo = function() { // This function expression is not reachable return 2; }; } alert(outerFunction()); // Displays 1 |
在非函数块(比如if)中编写函数声明是不安全的,因为它们是不可访问的。
1 2 3 | if (test) { function x() { doSomething(); } } |
如下所示的命名函数表达式,在版本9之前可能无法在internet Explorer浏览器中工作。
1 | var today = function today() {return new Date()} |
根据"在堆栈跟踪中显示命名函数"的参数,现代JavaScript引擎实际上非常能够表示匿名函数。
在撰写本文时,V8、SpiderMonkey、Chakra和Nitro总是按名称引用命名函数。如果匿名函数有标识符,那么它们几乎总是通过标识符引用匿名函数。
SpiderMonkey可以计算出从另一个函数返回的匿名函数的名称。剩下的不能。
如果您非常非常希望您的迭代器和成功回调函数显示在跟踪中,您也可以将它们命名为…
1 | [].forEach(function iterator() {}); |
但在大多数情况下,这并不值得强调。
利用(小提琴)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | 'use strict'; var a = function () { throw new Error(); }, b = function b() { throw new Error(); }, c = function d() { throw new Error(); }, e = { f: a, g: b, h: c, i: function () { throw new Error(); }, j: function j() { throw new Error(); }, k: function l() { throw new Error(); } }, m = (function () { return function () { throw new Error(); }; }()), n = (function () { return function n() { throw new Error(); }; }()), o = (function () { return function p() { throw new Error(); }; }()); console.log([a, b, c].concat(Object.keys(e).reduce(function (values, key) { return values.concat(e[key]); }, [])).concat([m, n, o]).reduce(function (logs, func) { try { func(); } catch (error) { return logs.concat('func.name: ' + func.name + ' ' + 'Trace: ' + error.stack); // Need to manually log the error object in Nitro. } }, []).join(' ')); |
V8
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | func.name: Trace: Error at a (http://localhost:8000/test.js:4:11) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: b Trace: Error at b (http://localhost:8000/test.js:7:15) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: d Trace: Error at d (http://localhost:8000/test.js:10:15) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: Trace: Error at a (http://localhost:8000/test.js:4:11) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: b Trace: Error at b (http://localhost:8000/test.js:7:15) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: d Trace: Error at d (http://localhost:8000/test.js:10:15) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: Trace: Error at e.i (http://localhost:8000/test.js:17:19) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: j Trace: Error at j (http://localhost:8000/test.js:20:19) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: l Trace: Error at l (http://localhost:8000/test.js:23:19) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: Trace: Error at http://localhost:8000/test.js:28:19 at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: n Trace: Error at n (http://localhost:8000/test.js:33:19) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 func.name: p Trace: Error at p (http://localhost:8000/test.js:38:19) at http://localhost:8000/test.js:47:9 at Array.reduce (native) at http://localhost:8000/test.js:44:27 test.js:42 |
SpiderMonkey
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | func.name: Trace: a@http://localhost:8000/test.js:4:5 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: b Trace: b@http://localhost:8000/test.js:7:9 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: d Trace: d@http://localhost:8000/test.js:10:9 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: Trace: a@http://localhost:8000/test.js:4:5 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: b Trace: b@http://localhost:8000/test.js:7:9 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: d Trace: d@http://localhost:8000/test.js:10:9 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: Trace: e.i@http://localhost:8000/test.js:17:13 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: j Trace: j@http://localhost:8000/test.js:20:13 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: l Trace: l@http://localhost:8000/test.js:23:13 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: Trace: m</<@http://localhost:8000/test.js:28:13 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: n Trace: n@http://localhost:8000/test.js:33:13 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 func.name: p Trace: p@http://localhost:8000/test.js:38:13 @http://localhost:8000/test.js:47:9 @http://localhost:8000/test.js:54:1 |
脉轮
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 | func.name: undefined Trace: Error at a (http://localhost:8000/test.js:4:5) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at b (http://localhost:8000/test.js:7:9) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at d (http://localhost:8000/test.js:10:9) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at a (http://localhost:8000/test.js:4:5) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at b (http://localhost:8000/test.js:7:9) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at d (http://localhost:8000/test.js:10:9) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at e.i (http://localhost:8000/test.js:17:13) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at j (http://localhost:8000/test.js:20:13) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at l (http://localhost:8000/test.js:23:13) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at Anonymous function (http://localhost:8000/test.js:28:13) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at n (http://localhost:8000/test.js:33:13) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) func.name: undefined Trace: Error at p (http://localhost:8000/test.js:38:13) at Anonymous function (http://localhost:8000/test.js:47:9) at Global code (http://localhost:8000/test.js:42:1) |
硝基
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | func.name: Trace: a@http://localhost:8000/test.js:4:22 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: b Trace: b@http://localhost:8000/test.js:7:26 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: d Trace: d@http://localhost:8000/test.js:10:26 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: Trace: a@http://localhost:8000/test.js:4:22 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: b Trace: b@http://localhost:8000/test.js:7:26 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: d Trace: d@http://localhost:8000/test.js:10:26 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: Trace: i@http://localhost:8000/test.js:17:30 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: j Trace: j@http://localhost:8000/test.js:20:30 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: l Trace: l@http://localhost:8000/test.js:23:30 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: Trace: http://localhost:8000/test.js:28:30 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: n Trace: n@http://localhost:8000/test.js:33:30 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 func.name: p Trace: p@http://localhost:8000/test.js:38:30 http://localhost:8000/test.js:47:13 reduce@[native code] global code@http://localhost:8000/test.js:44:33 |
下面列出了两种不同的函数声明之间的四个值得注意的比较。
函数的可用性(范围)下面的工作是因为
1 2 3 4 5 6 7 8 9 | try { console.log("Success:", add(1, 1)); } catch(e) { console.log("ERROR:" + e); } function add(a, b){ return a + b; } |
下面的方法不起作用(因为
1 2 3 4 5 6 7 8 9 | try { console.log("Success:", add(1, 1)); } catch(e) { console.log("ERROR:" + e); } var add=function add(a, b){ return a + b; } |
下面的方法不起作用,因为
1 2 3 4 5 6 7 8 9 | try { console.log("Success:", add(1, 1)); } catch(e) { console.log("ERROR:" + e); } var add=function(a, b){ return a + b; } |
函数的名称
1 2 3 | function foobar(a, b){} console.log(foobar.name); |
1 2 3 | var a = function foobar(){}; console.log(a.name); |
否则,如果函数声明为
1 2 3 4 5 | var a = function(){}; var b = (function(){ return function(){} }); console.log(a.name); console.log(b.name); |
如果没有为函数设置变量,那么函数名就是空字符串(
1 | console.log((function(){}).name ===""); |
最后,虽然函数最初分配给的变量设置了名称,但是设置给函数的后续变量不会更改名称。
1 2 3 4 5 6 7 | var a = function(){}; var b = a; var c = b; console.log(a.name); console.log(b.name); console.log(c.name); |
在谷歌的V8和Firefox的Spidermonkey中,JIST编译可能会有一些微秒的差异,但最终的结果是完全相同的。为了证明这一点,让我们通过比较两个空白代码片段的速度来检查JSPerf在微基准测试中的效率。这里可以找到JSPerf测试。jsben。ch测试可以在这里找到。正如您所看到的,在本不应该存在差异的情况下,却存在明显的差异。如果您真的像我一样是个性能怪才,那么在尝试减少作用域中变量和函数的数量,特别是消除多态性(例如使用相同的变量存储两种不同类型)时,可能更值得您这样做。
变量可变性当您使用
1 2 3 4 5 6 7 8 9 10 11 | (function(){ "use strict"; var foobar = function(){}; // initial value try { foobar ="Hello World!"; // new value console.log("[no error]"); } catch(error) { console.log("ERROR:" + error.message); } console.log(foobar, window.foobar); })(); |
然而,当我们使用const-statement时,变量引用变成不可变的。这意味着我们不能给变量赋一个新值。但是,请注意,这并不会使变量的内容不可变:如果您执行
1 2 3 4 5 6 7 8 9 10 11 | (function(){ "use strict"; const foobar = function(){}; // initial value try { foobar ="Hello World!"; // new value console.log("[no error]"); } catch(error) { console.log("ERROR:" + error.message); } console.log(foobar, window.foobar); })(); |
有趣的是,如果我们将变量声明为
1 2 3 4 5 6 7 8 9 10 11 | (function(){ "use strict"; function foobar(){}; // initial value try { foobar ="Hello World!"; // new value console.log("[no error]"); } catch(error) { console.log("ERROR:" + error.message); } console.log(foobar, window.foobar); })(); |
"最近的块"是最近的"函数"(包括异步函数、生成器函数和异步生成器函数)。然而,有趣的是,在非闭包块中,
1 2 3 4 5 6 7 8 9 10 11 12 | try { // typeof will simply return"undefined" if the variable does not exist if (typeof add !=="undefined") { add(1, 1); // just to prove it console.log("Not a block"); }else if(add===undefined){ // this throws an exception if add doesn't exist console.log('Behaves like var add=function(a,b){return a+b}'); } } catch(e) { console.log("Is a block"); } var add=function(a, b){return a + b} |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | try { // typeof will simply return"undefined" if the variable does not exist if (typeof add !=="undefined") { add(1, 1); // just to prove it console.log("Not a block"); }else if(add===undefined){ // this throws an exception if add doesn't exist console.log('Behaves like var add=function(a,b){return a+b}') } } catch(e) { console.log("Is a block"); } function add(a, b){ return a + b; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | try { // typeof will simply return"undefined" if the variable does not exist if (typeof add !=="undefined") { add(1, 1); // just to prove it console.log("Not a block"); }else if(add===undefined){ // this throws an exception if add doesn't exist console.log('Behaves like var add=function(a,b){return a+b}') } } catch(e) { console.log("Is a block"); } (function () { function add(a, b){ return a + b; } })(); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | try { // typeof will simply return"undefined" if the variable does not exist if (typeof add !=="undefined") { add(1, 1); // just to prove it console.log("Not a block"); }else if(add===undefined){ // this throws an exception if add doesn't exist console.log('Behaves like var add=function(a,b){return a+b}') } } catch(e) { console.log("Is a block"); } { function add(a, b){ return a + b; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | try { // typeof will simply return"undefined" if the variable does not exist if (typeof add !=="undefined") { add(1, 1); // just to prove it console.log("Not a block"); }else if(add===undefined){ // this throws an exception if add doesn't exist console.log('Behaves like var add=function(a,b){return a+b}') } } catch(e) { console.log("Is a block"); } (() => { var add=function(a, b){ return a + b; } })(); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | try { // typeof will simply return"undefined" if the variable does not exist if (typeof add !=="undefined") { add(1, 1); // just to prove it console.log("Not a block"); }else if(add===undefined){ // this throws an exception if add doesn't exist console.log('Behaves like var add=function(a,b){return a+b}') } } catch(e) { console.log("Is a block"); } (() => { function add(a, b){ return a + b; } })(); |
在JavaScript中有两种方法来创建函数:
函数声明:
1 2 3 4 | function fn(){ console.log("Hello"); } fn(); |
这是非常基本的,不言自明的,在许多语言中使用,并且是跨C语言家族的标准。我们声明了一个定义它的函数,并通过调用它来执行它。
需要知道的是函数实际上是JavaScript中的对象;在内部,我们为上面的函数创建了一个对象,并给它一个名为fn的名称,或者对象的引用存储在fn中。函数是JavaScript中的对象;函数的实例实际上是一个对象实例。
函数表达式:
1 2 3 4 | var fn=function(){ console.log("Hello"); } fn(); |
JavaScript具有一流的函数,也就是说,创建一个函数并将其分配给一个变量,就像创建一个字符串或数字并将其分配给一个变量一样。这里,fn变量被分配给一个函数。这个概念的原因是函数是JavaScript中的对象;fn指向上述函数的对象实例。我们已经初始化了一个函数并将它赋值给一个变量。它没有执行函数并分配结果。
引用:JavaScript函数声明语法:var fn = function() {} vs function fn() {}
性能:
现在表达式和声明之间几乎没有区别。函数表达式现在似乎更快了。
Chrome 62.0.3202
FireFox 55
Chrome金丝雀63.0.3225
Anonymous function expressions appear to have better performance
againstNamed function expression.
火狐Chrome金丝雀铬
两者都是定义函数的不同方法。区别在于浏览器如何解释它们并将它们加载到执行上下文中。
第一种情况是函数表达式,它只在解释器到达这一行代码时加载。如果你像下面这样做,你会得到一个错误,函数1不是一个函数。
1 2 3 4 | functionOne(); var functionOne = function() { // Some code }; |
原因是第一行没有为functionOne分配任何值,因此它是未定义的。我们试图把它作为一个函数来调用,因此我们得到一个错误。
在第二行,我们将匿名函数的引用分配给functionOne。
第二种情况是在执行任何代码之前加载的函数声明。因此,如果您像下面这样做,就不会在代码执行前加载声明时出现任何错误。
1 2 3 4 | functionOne(); function functionOne() { // Some code } |
它们非常相似,只是有一些小的区别,第一个是变量赋值给一个匿名函数(函数声明),第二个是用JavaScript创建函数的常用方法(匿名函数声明),两者都有使用,优缺点:
1. 函数表达式
1 2 3 | var functionOne = function() { // Some code }; |
A Function Expression defines a function as a part of a larger
expression syntax (typically a variable assignment ). Functions
defined via Functions Expressions can be named or anonymous. Function
Expressions must not start with"function" (hence the parentheses
around the self invoking example below).
给变量分配一个函数,意味着没有提升,因为我们知道在JavaScript函数可以提升,意味着他们可以被称为之前宣布,而变量需要声明访问之前,在这种情况下,所以就意味着我们不能访问函数的声明之前,也可以是一种编写函数,函数返回另一个函数,这样的声明可以有意义,也在ECMA6,上面你可以把它分配给一个箭头函数,这个函数可以用来调用匿名函数,这种声明方式也是在JavaScript中创建构造函数的一种更好的方式。
2. 函数声明
1 2 3 | function functionTwo() { // Some code } |
A Function Declaration defines a named function variable without
requiring variable assignment. Function Declarations occur as
standalone constructs and cannot be nested within non-function blocks.
It’s helpful to think of them as siblings of Variable Declarations.
Just as Variable Declarations must start with"var", Function
Declarations must begin with"function".
这是正常的方式调用JavaScript函数,这个函数可以调用在你即使是在JavaScript函数声明它升起,但是如果你有使用严格的这不会提升正如预期的那样,这是一个好方法调用所有正常的功能并不大也行是一个构造函数。
另外,如果你需要更多关于提升在JavaScript中的工作原理的信息,请访问下面的链接:
https://developer.mozilla.org/en-US/docs/Glossary/Hoisting
这只是声明函数的两种可能的方法,第二种方法是在声明之前使用函数。
1 2 3 4 5 6 7 8 | var func = new Function("x","y","return x*y;"); function secondFunction(){ var result; result = func(10,20); console.log ( result ); } secondFunction() |
这叫做函数表达式:
1 2 3 4 5 6 7 | var getRectArea = function(width, height) { return width * height; }; console.log("Area of Rectangle:" + getRectArea(3,4)); // This should return the following result in the console: // Area of Rectangle: 12 |
这叫做函数声明:
1 2 3 4 5 6 7 8 9 10 11 | var w = 5; var h = 6; function RectArea(width, height) { //declaring the function return area = width * height; } //note you do not need ; after } RectArea(w,h); //calling or executing the function console.log("Area of Rectangle:" + area); // This should return the following result in the console: // Area of Rectangle: 30 |
希望这有助于解释函数表达式和函数声明之间的区别以及如何使用它们。谢谢。
我更喜欢将函数定义为变量:
1 2 3 | let first = function(x){ return x[0]; } |
而不是:
1 2 3 | function first(){ .... } |
因为我可以在定义函数时使用表达式和装饰器。例如:
1 2 3 4 | let safe = function(f){ try {f()...} } let last = safe(function(x){return x[0]}). |
ES6也更短:
1 2 3 4 5 6 7 8 | let last = x => x[0] ........... function last(x){ return x[0]; } ...... let last = safe(x => x[0]); |
第一个是匿名函数表达式,第二个是函数声明。匿名函数没有名称。匿名函数表达式和函数语句的主要区别在于函数名。
命名函数Vs.匿名函数匿名函数快速且易于键入,许多库和工具都倾向于采用这种惯用的代码风格。但是,匿名函数也有一些缺点:
可读性:匿名函数省略了可能导致代码可读性较差的名称。
调试:匿名函数在堆栈跟踪中没有有用的名称,这使得调试更加困难。
自引用:如果函数需要引用自身,例如递归,该怎么办?
命名函数表达式:
为函数表达式提供一个名称非常有效地解决了所有这些缺点,并且没有明显的缺点。最佳实践是总是为函数表达式命名:
1 2 3 | setTimeout(function timeHandler() { // <-- look, a name here! console.log("I've waited 1 second"); }, 1000); |
命名IIFEs(立即调用的函数表达式):
1 2 3 | (function IIFE(str) { // <-- look, always name IIFEs! console.log(str); //"Hello!" })('Hello!'); |
对于赋值给变量的函数,在本例中为函数命名并不常见,可能会引起混淆,在本例中,箭头函数可能是更好的选择。
JS中的表达式:返回值的东西例子:尝试以下chrome控制台:
1 2 3 4 5 | a = 10 output : 10 (1 + 3) output = 4 |
声明/语句:不返回值的东西例子:
1 2 3 | if (1 > 2) { // do something. } |
这里(1>2)是一个表达式,但是"if"语句不是。它不返回任何东西。
类似地,我们有函数声明/语句vs函数表达式举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | // test.js var a = 10; // function expression var fun_expression = function() { console.log("Running function Expression"); } // funciton expression function fun_declaration() { console.log("Running function Statement"); } |
重要的是:当JavaScript引擎运行上面的js文件时会发生什么。
当这个js运行时会发生以下事情:
内存将创建变量'a'和'fun_expression'。内存将被创建为函数语句'fun_declaration'"a"将被赋值为"undefined"。'fun_expression'将被赋值为'undefined'。'fun_declaration'将完整地保存在内存中。注意:上面的第1步和第2步称为"执行上下文创建阶段"。现在假设我们将js更新为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // test.js console.log(a) //output: udefined (No error) console.log(fun_expression) // output: undefined (No error) console.log(fun_expression()) // output: Error. As we trying to invoke undefined. console.log(fun_declaration()) // output: running function statement (As fun_declaration is already hoisted in the memory). var a = 10; // function expression var fun_expression = function() { console.log('Running function expression') } // function declaration function fun_declaration() { console.log('running function declaration') } console.log(a) // output: 10 console.log(fun_expression()) //output: Running function expression console.log(fun_declaration()) //output: running function declaration |
注释中提到的输出,应该有助于理解函数表达式和函数语句/声明之间的区别。
需要注意的重要一点是:-
假设有两个函数:-
1 2 3 4 5 | sum(1,2); const sum = function(first, second) { return first + second; } |
在上面的例子中,它会给出一个错误,sum没有定义,但是
1 2 3 4 5 | sum(1,2); function sum(first, second) { return first + second; } |
此功能不会有任何错误,因为在这种情况下将发生吊装。
这两个函数的另一个区别是functionOne可以用作一个变量,它可以包含多个函数,functionTwo可以包含一些代码块,这些代码块在调用时全部执行。请检查以下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | var functionOne = (function() { return { sayHello: function(){ console.log('say hello') }, redirectPage:function(_url){ window.location.href = _url; } } })(); |
您可以选择调用哪个函数。e。g functionOne。sayHello或functionOne。redirectPage。如果调用function2,整个代码块就会被执行。
函数声明和表达式之间的提升行为很重要,但两者之间还有另一个区别:
函数在条件语句中MDN推荐的一般实践是使用函数表达式而不是在
免责声明:macOS不能运行Microsoft Edge,所以我无法验证。
//函数声明示例var ed ="foo";控制台。原木(" foo' name ${ed ?")"is":"is not