javascript - var functionname = function( ) {} VS function functionName( ) {}

  显示原文与译文双语对照的内容

我最近开始维护别人的JavaScript代码。 我正在修复 Bug,添加特性并尝试整理代码,使它的更加一致。

以前的开发人员使用两种声明函数的方法,如果有原因,我无法解决。

这两种方法有:


var functionOne = function() {
//Some code
};


function functionTwo() {
//Some code
}

使用这两种不同方法的原因是什么,每种方法的优缺点是什么? 对于一个不能用另一个方法完成的方法,有什么可以做的?

时间:

区别在于 functionOne 是在run-time定义的,而 functionTwo 是在parse-time上定义的脚本块。 例如:


<script>
//Error
 functionOne();

 var functionOne = function() {
 };
</script>

<script>
//No error
 functionTwo();

 function functionTwo() {
 }
</script>

这也意味着在 Strict 模式下,你不能使用第二个语法来定义函数:


<script>
"use strict";
 if (test) {
//Error
 function functionThree() { doSomething(); }
 }
</script>

没有 "use strict",这将不会导致一个错误,而 functionThree 将被定义为不考虑 test 值。

首先我要更正 Greg: function abc(){} 范围太 — abc 名字定义在遇到这个定义的范围。 例如:


function xyz(){
 function abc(){};
//abc is defined here...
}
//...but not here

其次,可以同时组合两种样式:


var xyz = function abc(){};

xyz 将被定义为通常,abc 在所有浏览器中都未定义,但 IE —不依赖于它。 但它将在它的体内定义:


var xyz = function abc(){
//xyz is visible here
//abc is visible here
}
//xyz is visible here
//abc is undefined here

如果你想在所有浏览器上使用别名,请使用这种声明:


function abc(){};
var xyz = abc;

在本例中,xyzabc 都是同一个对象的别名:


console.log(xyz === abc);//prints"true"

一个令人信服的理由去使用组合样式是 function objects ( not supported by ie )"姓名"属性。 基本上当你定义这样的函数时:


function abc(){};
console.log(abc.name);//prints"abc"

它的名字被自动分配。 但是当你这样定义它时:


var abc = function(){};
console.log(abc.name);//prints""

它的名字是空的—我们创建了一个匿名函数并将它的分配给一些变量。

使用组合样式的另一个好理由是使用短的内部名称引用自身,同时为外部用户提供一个长的non-conflicting名称:


//assume really.long.external.scoped is {}
really.long.external.scoped.name = function shortcut(n){
//let's call itself recursively:
 shortcut(n - 1);
//...
//let's pass itself as a callback:
 someFunction(shortcut);
//...
}

在上面的例子中,我们可以用一个外部名称来做同样的事情,但是它太笨拙了。

( 引用自身的另一种方法是使用 arguments.callee,它仍然相对较长,在严格模式下不支持。)

深入的JavaScript处理两个语句不同。 这是一个函数声明:

 
function abc(){}

 

abc 在当前范围内定义了:


//we can call it here
abc();//works
//yet it is defined down there
function abc(){}
//we can call it again
abc();//works

这是一个函数表达式:


var xyz = function(){};

xyz 是从赋值点定义的:


//we can't call it here
xyz();//UNDEFINED!!!
//now it is defined
xyz = function(){}
//we can call it here
xyz();//works

函数声明 vs 函数表达式是Greg演示的差异的真正原因。

有趣的事实:


var xyz = function abc(){};
console.log(xyz.name);//prints"abc"

我个人喜欢"函数表达式"声明,因为这样我可以控制可见性。 当我定义函数时:


var abc = function(){};

我知道我在本地定义了函数。 当我定义函数时:

 
abc = function(){};

 

我知道我全局定义了它,因为我没有在作用域链的任何地方定义 abc 。 这种样式的定义即使在 eval() 内部也是有弹性的。 而这个定义:

 
function abc(){};

 

取决于上下文,可能会让你猜测实际定义的位置,尤其是在 eval() —的情况下,答案是: 这要看浏览器了。

谈到全球背景下,两者, var 语句和 FunctionDeclaration 结束时将创建一个 non-deleteable全局对象属性,但都可以被重写的价值。

两种方式之间的细微差异,当变量实例化流程运行( 在实际代码执行之前) 声明的所有标识符与 undefinedvar 将被初始化,和那些 FunctionDeclaration 所使用的将是可用的,因为那一刻,例如:


 alert(typeof foo);//'function', it's already available
 alert(typeof bar);//'undefined'
 function foo () {}
 var bar = function () {};
 alert(typeof bar);//'function'

barFunctionExpression的赋值一直到运行时。

FunctionDeclaration 创建的全局属性可以覆盖,而不需要任何问题就像变量值 e.g.:


 function test () {}
 test = null;

两个示例之间的另一个明显区别是第一个函数没有名称,但是第二个函数有名称,这在调试时非常有用( 例如 。 检查调用堆栈) 。

关于你编辑的第一个示例( foo = function() { alert('hello!'); }; ),这是一个未声明的赋值,我强烈建议你总是使用 var 关键字。

有了赋值,没有 var 语句,如果引用的标识符在作用域链中没有找到,它将成为全局对象的deleteable 属性。

同时,未申报作业扔 ReferenceError 在ecmascript 5 严格模式。

必须读取:

注意注释: 这个答案是从合并的另一个问题,OP中的主要疑问和误解是用 FunctionDeclaration 声明的标识符,而不是大小写。

你所发布的两个代码 Fragment,几乎所有的目的都是一样的。

但是,行为的差异是,第一个变体( var functionOne = function() {} ),该函数只能在代码中的那个点之后调用。

使用第二个变量( function functionTwo() ),函数可以用于在声明函数的上面运行的代码。

这是因为在运行时,函数被分配给变量 foo 。 第二,在解析时将函数分配给那个标识符 foo 。

技术信息

Javascript有三种定义函数的方法。

  1. 你的第一个代码片段显示了一个函数表达式。 这涉及到使用 "函数"操作符创建一个函数- 运算符的结果可以存储在任何变量或者对象属性中。 函数表达式是强大的。 函数表达式通常称为"匿名函数",因为它不必有名称,
  2. 你的第二个示例是一个函数声明 。 这将使用 "函数"语句创建一个函数。 函数在分析时可用,可以在该作用域的任何地方调用。 以后仍可以将它的存储在变量或者对象属性中。
  3. 定义函数的第三种方法是 "函数( 函数) )"构造函数,它在你的原始日志中没有显示。 不推荐使用它,因为它使用的方式与 eval(), 有它的问题。

下面是创建函数的标准表单的摘要: ( 原本是为另一个问题而写的,但在被移入标准问题后改编。)

函数声明

第一个表单是一个函数声明,它看起来像:


function x() {
 console.log('x');
}

函数声明是声明 ;它不是语句或者表达式。 因此,你不需要使用 ; ( 虽然这样做是无害的) 进行跟踪。

处理函数声明当执行进入环境似乎之前任何一步一步的执行代码。 其创建的函数( 上面示例中的x ) 给出一个合适的名字,这名字是宣言出现的范围。

因为在同一上下文中的任何分步代码之前处理它,你可以执行如下操作:


x();//Works even though it's 上面 the declaration
function x() {
 console.log('x');
}

也因为它不是按部就班地执行代码的一部分,你不能把它放进一个控制结构像 tryifswitchwhile 等等。


if (someCondition) {
 function foo() {//<===== INVALID AND WILL FAIL ON
 }//MANY ENGINES
}

一些引擎将处理上面的,尽管它是无效的,通过重写on-the-fly函数表达式。 有人说函数声明添加到下一个规范( ECMAScript6 ) 编纂。 但使用当前引擎,它不能可靠工作;不要这样做。

匿名函数表达式

第二个常见形式称为匿名函数表达式:


var y = function () {
 console.log('y');
};

这里创建的函数没有名称( 这是匿名的) 。 像所有的表情,这是评估时达到逐步执行的代码。

我应该注意到当前草案下版本的javascript标准,ECMAScript6会分配一个名称,函数通过推断它的上下文。 本例中的名称是 y 。 ( Firefox's ) mozilla的javascript引擎,已经和将会在接下来的规范。 基本上每次解析器就可以合理地猜测,正如上面可以的,它将( 一旦引擎完成新的事情) 。

命名函数表达式

第三种形式是命名函数表达式 ("nfe"):


var z = function w() {
 console.log('zw')
};

创建的函数具有一个正确的名称( w 在本例中) 。 像所有的表情,这是评估时达到逐步执行的代码。 函数的名称不是添加到范围的表达出现;名称函数本身的范围:


var z = function w() {
 console.log(typeof w);//"function"
};
console.log(typeof w);//"undefined"

注意NFEs常常是JavaScript实现的Bug的源。 ie8,早些时候,例如,处理 NFEs 完全错了,在两个不同的时间创建两个不同的功能。 早期版本的Safari也有问题。 好消息是当前版本的浏览器( IE9和向上,当前的Safari ) 不再有这些问题了。 (但在撰写本文时,不幸的是,ie8仍在广泛使用,所以用NFEs代码web一般来说仍是个问题。)

其他评论者已经覆盖了上两个变体的语义差异。 我想注意一种风格差异: 只有"赋值"变体可以设置另一个对象的属性。

我经常使用这样的模式构建JavaScript模块:


(function(){
 var exports = {};

 function privateUtil() {
. . .
 }

 exports.publicUtil = function() {
. . .
 };

 return exports;
})();

使用这里模式,你的公共函数将使用赋值,而私有函数使用声明。

( 另请注意,赋值应在语句之后需要分号,而声明则禁止它。)

的回答的更好解释


functionTwo();
function functionTwo() {
}

为什么没有错误?我们总是教导表达式excuted从上到下??

因为:

函数声明和变量声明总是被JavaScript解释器隐藏到它们包含范围的顶部。 函数参数和language-defined名称显然已经存在。 cherry

这意味着这样的代码:


functionOne(); --------------- var functionOne;
 | is actually | functionOne(); 
var functionOne = function(){ | interpreted |--> 
}; | like | functionOne = function(){
 --------------- };

注意声明的赋值部分未被提升。 只有名称被提升。

但在函数声明中,整个函数体也将被提升。


functionTwo(); --------------- function functionTwo() {
 | is actually | };
function functionTwo() { | interpreted |--> 
} | like | functionTwo();
 --------------- 

一个重要原因是添加一个变量作为命名空间的"根"。


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

}

或者


var MyNamespace {
 foo: function() {
 },
. . .
}

有许多用于命名空间的技术。 使用过多的JavaScript模块变得更加重要。

另请参见 Javascript命名空间声明

当你需要避免重写以前的函数定义时,在第二个方法上首选什么时候更可取。

使用:


if (condition){
 function myfunction(){
//some code 
 }
}

myfunction的这个定义将覆盖以前的定义,因为它将在 parse-time 。

同时:


if (condition){
 var myfunction = function (){
//some code
 }
}

正确的工作定义 myfunction 只有 condition 满足。

一个函数声明和分配给变量的函数表达式在绑定建立后行为相同。

有区别但是如何和当函数对象实际上是相关的变量。 这种差异是由于在JavaScript中调用变量提升的机制。

基本上,所有的函数声明和变量声明吊的顶部发生( 这就是为什么我们说JavaScript有函数范围 ) 函数的声明。

  • 函数声明时升起,函数体"跟随"当评估函数体,该变量将立即被绑定到一个函数对象。

  • 升起,变量声明时初始化不效仿,但"靠左"。 变量被初始化为 undefined 在函数体的开始,并将分配一个值在其原始位置代码。 (实际上,它将被分配一个值在每个位置变量的声明名称相同的发生。)

提升的顺序也很重要: 函数声明优先于具有相同名称的变量声明,最后一个函数声明优先于以前具有相同名称的函数声明。

一些例子。。


var foo = 1;
function bar() {
 if (!foo) {
 var foo = 10 }
 return foo; }
bar()//10

变量 foo 被提升到函数顶部,初始化为 undefined,这样 footrue,所以 foo 被指定 10 。! 外的foobar 扮演任何角色,不变的范围。


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

函数声明优先于变量声明,最后一个函数声明"置顶"。


function f() {
 var a = 4;
 function a() {return 1}; 
 function a() {return 2}; 
 return a; }
f()//4

在本例中,a 通过计算第二个函数声明产生的函数对象初始化,然后被指定为 4


var a = 1;
function b() {
 a = 10;
 return;
 function a() {}}
b();
a//1

这里首先提升函数声明,声明和初始化变量 a 。 接下来,这个变量被分配给 10 。 换句话说:赋值不分配给外部变量 a

...