问题 var functionName = function(){} vs function functionName(){}
我最近开始维护其他人的JavaScript代码。我正在修复错误,添加功能,并尝试整理代码并使其更加一致。
以前的开发人员使用两种声明函数的方法,如果背后有原因,我就无法解决。
这两种方式是:
var functionOne = function() {
// Some code
};
function functionTwo() {
// Some code
}
使用这两种不同方法的原因是什么?每种方法的优缺点是什么?有一种方法可以通过一种方法完成,而另一种方法无法做到吗?
6565
2017-12-03 11:31
起源
答案:
不同之处在于 functionOne
是一个函数表达式,因此仅在到达该行时定义,而 functionTwo
是一个函数声明,并在其周围的函数或脚本执行时定义(由于 吊装)。
例如,一个函数表达式:
// TypeError: functionOne is not a function
functionOne();
var functionOne = function() {
console.log("Hello!");
};
并且,一个函数声明:
// Outputs: "Hello!"
functionTwo();
function functionTwo() {
console.log("Hello!");
}
这也意味着您无法使用函数声明有条件地定义函数:
if (test) {
// Error or misbehavior
function functionThree() { doSomething(); }
}
以上实际上定义了 functionThree
无论如何 test
的价值 - 除非 use strict
是有效的,在这种情况下,它只是引发一个错误。
4493
2017-12-03 11:37
首先,我想纠正格雷格: function abc(){}
也是范围 - 名称 abc
在遇到此定义的范围内定义。例:
function xyz(){
function abc(){};
// abc is defined here...
}
// ...but not here
其次,可以结合两种风格:
var xyz = function abc(){};
xyz
将像往常一样定义, abc
在所有浏览器中都未定义,但Internet Explorer - 不依赖于它的定义。但它将在其内部定义:
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;
在这种情况下,两者 xyz
和 abc
是同一对象的别名:
console.log(xyz === abc); // prints "true"
使用组合样式的一个令人信服的理由是函数对象的“名称”属性(Internet Explorer不支持)。基本上当你定义一个像这样的函数
function abc(){};
console.log(abc.name); // prints "abc"
其名称将自动分配。但是当你定义它时
var abc = function(){};
console.log(abc.name); // prints ""
它的名称是空的 - 我们创建了一个匿名函数并将其分配给某个变量。
使用组合样式的另一个好理由是使用简短的内部名称来引用自身,同时为外部用户提供长的非冲突名称:
// 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);
// ...
}
在上面的示例中,我们可以使用外部名称执行相同操作,但它会过于笨重(并且速度较慢)。
(引用自身的另一种方式是使用 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
而且,它通过一个悬挂 return
声明:
// We can call it here
abc(); // Works
return;
function abc(){}
这是一个函数表达式:
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
函数声明与函数表达式是Greg证明存在差异的真正原因。
有趣的事实:
var xyz = function abc(){};
console.log(xyz.name); // Prints "abc"
就个人而言,我更喜欢“函数表达式”声明,因为这样我可以控制可见性。当我定义函数时
var abc = function(){};
我知道我在本地定义了这个函数。当我定义函数时
abc = function(){};
我知道我在全球范围内定义它,但我没有定义 abc
范围链中的任何地方。这种定义风格即使在内部使用也具有弹性 eval()
。而定义
function abc(){};
取决于上下文,可能会让你猜测它实际定义的位置,特别是在情况下 eval()
- 答案是:这取决于浏览器。
1805
2017-12-03 17:43
这是创建函数的标准表单的纲要: (最初是针对另一个问题编写的,但在被移入规范问题后进行了调整。)
条款:
快速清单:
功能声明
“匿名” function
表达 (尽管有这个术语,有时会创建带有名称的函数)
命名 function
表达
存取器功能初始化器(ES5 +)
箭头功能表达(ES2015 +) (与匿名函数表达式一样,不涉及显式名称,但可以使用名称创建函数)
对象初始化程序中的方法声明(ES2015 +)
中的构造函数和方法声明 class
(ES2015 +)
功能声明
第一种形式是 功能声明,看起来像这样:
function x() {
console.log('x');
}
函数声明是一个 宣言;这不是陈述或表达。因此,你不遵循它 ;
(虽然这样做是无害的)。
当执行进入它出现的上下文时,处理函数声明, 之前 执行任何分步代码。它创建的功能给出了一个合适的名称(x
在上面的例子中),该名称放在声明出现的范围内。
因为它是在同一个上下文中的任何分步代码之前处理的,所以你可以这样做:
x(); // Works even though it's above the declaration
function x() {
console.log('x');
}
在ES2015之前,如果你将一个函数声明放在一个控制结构中,那么规范并未涵盖JavaScript引擎应该做的事情 try
, if
, switch
, while
等等,像这样:
if (someCondition) {
function foo() { // <===== HERE THERE
} // <===== BE DRAGONS
}
因为他们被处理了 之前 一步一步的代码运行,知道当他们在一个控制结构时该怎么做是很棘手的。
虽然这样做不是 规定 直到ES2015,这是一个 允许的延期 支持块中的函数声明。不幸的是(并且不可避免地),不同的引擎做了不同的事情。
从ES2015开始,规范说明了该怎么做。事实上,它提供了三个单独的事情:
- 如果处于松散模式 不 在Web浏览器上,JavaScript引擎应该做一件事
- 如果在Web浏览器上处于松散模式,则JavaScript引擎应该执行其他操作
- 如果在 严格 模式(浏览器与否),JavaScript引擎应该做另一件事
松散模式的规则很棘手,但在 严格 模式,块中的函数声明很容易:它们是块的本地(它们具有 阻止范围这也是ES2015中的新功能,并且它们被提升到了最高层。所以:
"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)
“匿名” function
表达
第二种常见形式称为 匿名函数表达式:
var y = function () {
console.log('y');
};
与所有表达式一样,它是在逐步执行代码时达到的。
在ES5中,它创建的函数没有名称(它是匿名的)。在ES2015中,如果可能,通过从上下文推断出该函数的名称。在上面的示例中,名称将是 y
。当函数是属性初始值设定项的值时,会执行类似的操作。 (有关何时发生这种情况以及规则的详细信息,请搜索 SetFunctionName
在里面 规范- 它出现 遍 这个地方。)
命名 function
表达
第三种形式是 命名函数表达式 ( “NFE”):
var z = function w() {
console.log('zw')
};
这个创建的函数有一个合适的名字(w
在这种情况下)。与所有表达式一样,在逐步执行代码时会对其进行评估。该函数的名称是 不 添加到表达式出现的范围;名字 是 在函数本身的范围内:
var z = function w() {
console.log(typeof w); // "function"
};
console.log(typeof w); // "undefined"
请注意,NFE经常成为JavaScript实现的错误来源。例如,IE8和更早版本处理NFE 完全不正确,在两个不同的时间创建两个不同的功能。早期版本的Safari也存在问题。好消息是当前版本的浏览器(IE9及更高版本,当前的Safari)不再存在这些问题。 (但在撰写本文时,遗憾的是,IE8仍然广泛使用,因此使用NFE和Web代码一般仍然存在问题。)
存取器功能初始化器(ES5 +)
有时功能可以潜入大部分未被注意到;就是这样的 访问者功能。这是一个例子:
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"
请注意,当我使用该功能时,我没有使用 ()
!那是因为它是一个 访问器功能 对于一个财产。我们以正常方式获取并设置属性,但在幕后,调用该函数。
您还可以使用创建访问器功能 Object.defineProperty
, Object.defineProperties
,以及鲜为人知的第二个论点 Object.create
。
箭头功能表达(ES2015 +)
ES2015给我们带来了 箭头功能。这是一个例子:
var a = [1, 2, 3];
var b = a.map(n => n * 2);
console.log(b.join(", ")); // 2, 4, 6
看到了 n => n * 2
藏在里面的东西 map()
呼叫?这是一个功能。
关于箭头功能的一些事情:
他们没有自己的 this
。相反,他们 关闭 该 this
他们被定义的背景。 (他们也关闭了 arguments
并且,相关的, super
。)这意味着 this
在他们内部是相同的 this
他们在哪里创建,不能改变。
正如您已经注意到上述内容,您不使用关键字 function
;相反,你使用 =>
。
该 n => n * 2
上面的例子是它们的一种形式。如果您有多个参数来传递函数,则使用parens:
var a = [1, 2, 3];
var b = a.map((n, i) => n * i);
console.log(b.join(", ")); // 0, 2, 6
(记住那个 Array#map
将条目作为第一个参数传递,将索引作为第二个参数传递。)
在这两种情况下,函数的主体只是一个表达式;函数的返回值将自动成为该表达式的结果(您不使用显式 return
)。
如果您所做的不仅仅是单个表达式,请使用 {}
一个明确的 return
(如果你需要返回一个值),正常情况下:
var a = [
{first: "Joe", last: "Bloggs"},
{first: "Albert", last: "Bloggs"},
{first: "Mary", last: "Albright"}
];
a = a.sort((a, b) => {
var rv = a.last.localeCompare(b.last);
if (rv === 0) {
rv = a.first.localeCompare(b.first);
}
return rv;
});
console.log(JSON.stringify(a));
没有的版本 { ... }
被称为箭头函数 表达体 要么 简洁的身体。 (也 简洁 箭头功能。)一个用 { ... }
定义主体是带箭头的箭头功能 功能体。 (也 详细 箭头功能。)
对象初始化程序中的方法声明(ES2015 +)
ES2015允许更短的形式声明引用函数的属性;它看起来像这样:
var o = {
foo() {
}
};
ES5及更早版本中的等价物是:
var o = {
foo: function foo() {
}
};
中的构造函数和方法声明 class
(ES2015 +)
ES2015带给我们 class
语法,包括声明的构造函数和方法:
class Person {
constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {
return this.firstName + " " + this.lastName;
}
}
上面有两个函数声明:一个用于构造函数,它获取名称 Person
和一个 getFullName
,这是一个分配给的功能 Person.prototype
。
544
2018-03-04 13:35
谈到全球背景,两者都有 var
声明和 FunctionDeclaration
最后会创造一个 非可删除 全局对象上的属性,但两者的值 可以被覆盖。
两种方式之间的细微差别在于 变量实例化 进程运行(在实际代码执行之前)所有使用的标识符 var
将初始化为 undefined
,和那些使用的 FunctionDeclaration
从那时起,它将可用,例如:
alert(typeof foo); // 'function', it's already available
alert(typeof bar); // 'undefined'
function foo () {}
var bar = function () {};
alert(typeof bar); // 'function'
分配 bar
FunctionExpression
发生在运行时间。
由a创建的全局属性 FunctionDeclaration
可以像变量值一样被覆盖而没有任何问题,例如:
function test () {}
test = null;
两个示例之间的另一个明显区别是第一个函数没有名称,但第二个函数有它,这在调试(即检查调用堆栈)时非常有用。
关于你编辑的第一个例子(foo = function() { alert('hello!'); };
),这是一个未经宣布的任务,我强烈建议你永远使用 var
关键词。
有了作业,没有 var
声明,如果在作用域链中找不到引用的标识符,它将成为一个 可删除 全局对象的属性。
此外,未申报的任务投掷 ReferenceError
在ECMAScript 5下 严格的模式。
必读:
注意:这个答案已合并 另一个问题,其中OP的主要疑问和误解是用a表示的标识符 FunctionDeclaration
,不能被覆盖,但事实并非如此。
133
2017-08-08 19:32
您在那里发布的两个代码片段几乎在所有目的下都会以相同的方式运行。
但是,行为的差异在于第一个变体(var functionOne = function() {}
),该函数只能在代码中的该点之后调用。
随着第二个变种(function functionTwo()
),该函数可用于在声明函数的上方运行的代码。
这是因为对于第一个变体,函数被赋值给变量 foo
在运行时。在第二个中,该功能被分配给该标识符, foo
,在解析时。
更多技术信息
JavaScript有三种定义函数的方法。
- 你的第一个片段显示了一个 功能表达。这涉及到使用 “功能”操作员 创建一个函数 - 该运算符的结果可以存储在任何变量或对象属性中。函数表达式就是这样强大的。函数表达式通常称为“匿名函数”,因为它不必具有名称,
- 你的第二个例子是 功能声明。这使用了 “功能”声明 创建一个功能。该函数在分析时可用,并且可以在该范围内的任何位置调用。您以后仍可以将其存储在变量或对象属性中。
- 定义函数的第三种方法是 “Function()”构造函数,原始帖子中未显示。建议不要使用它,因为它的工作方式与之相同
eval()
,这有它的问题。
111
2018-04-20 04:54
更好的解释 格雷格的回答
functionTwo();
function functionTwo() {
}
为什么没有错误?我们总是被告知表达式是从上到下执行的(??)
因为:
函数声明和变量声明总是被移动(hoisted
)JavaScript解释器无形地到达其包含范围的顶部。显然,功能参数和语言定义的名称已经存在。 本樱桃
这意味着代码如下:
functionOne(); --------------- var functionOne;
| is actually | functionOne();
var functionOne = function(){ | interpreted |-->
}; | like | functionOne = function(){
--------------- };
请注意,声明的赋值部分未被提升。只有名字被悬挂。
但在函数声明的情况下,整个函数体也将被提升:
functionTwo(); --------------- function functionTwo() {
| is actually | };
function functionTwo() { | interpreted |-->
} | like | functionTwo();
---------------
91
2017-08-09 02:45
其他评论者已经涵盖了上述两种变体的语义差异。我想要注意一个风格差异:只有“赋值”变体可以设置另一个对象的属性。
我经常用这样的模式构建JavaScript模块:
(function(){
var exports = {};
function privateUtil() {
...
}
exports.publicUtil = function() {
...
};
return exports;
})();
使用此模式,您的公共函数将全部使用赋值,而您的私有函数使用声明。
(另请注意,赋值在语句后应该使用分号,而声明禁止它。)
83
2018-03-03 19:19
何时优先考虑第一种方法到第二种方法的说明是当您需要避免覆盖函数的先前定义时。
同
if (condition){
function myfunction(){
// Some code
}
}
,这个定义 myfunction
将覆盖任何先前的定义,因为它将在分析时完成。
而
if (condition){
var myfunction = function (){
// Some code
}
}
做正确的定义工作 myfunction
只有当 condition
满足了。
68
2018-03-29 13:26
一个重要原因是添加一个且只有一个变量作为命名空间的“根”...
var MyNamespace = {}
MyNamespace.foo= function() {
}
要么
var MyNamespace = {
foo: function() {
},
...
}
命名空间有很多技巧。随着大量JavaScript模块的出现,它变得越来越重要。
另见 如何在JavaScript中声明命名空间?
55
2017-08-08 19:44