题 var functionName = function(){} vs function functionName(){}


我最近开始维护其他人的JavaScript代码。我正在修复错误,添加功能,并尝试整理代码并使其更加一致。

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

这两种方式是:

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

使用这两种不同方法的原因是什么?每种方法的优缺点是什么?有一种方法可以通过一种方法完成,而另一种方法无法做到吗?


6059
2017-12-03 11:31


起源


permadi.com/tutorial/jsFunc/index.html 关于javascript函数的页面非常好 - uzay95
相关的就是这个 优秀 文章 命名函数表达式。 - Phrogz
@CMS引用了这篇文章: kangax.github.com/nfe/#expr-vs-decl - Upperstage
您需要注意两件事:#1在JavaScript中,声明被提升。意思是 var a = 1; var b = 2; 变 var a; var b; a = 1; b = 2。因此,当您声明functionOne时,它会被声明但它的值不会立即设置。因为functionTwo只是一个声明,所以它放在范围的顶部。 #2 functionTwo允许您访问name属性,这在尝试调试某些内容时会有很大帮助。 - xavierm02
哦,顺便说一句,正确的语法是用“;”在转让之后并且在宣布之后没有。例如。 function f(){} VS var f = function(){};。 - xavierm02


答案:


不同之处在于 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



@Greg:顺便说一句,差异不仅在于它们在不同时间被解析。基本上,你的 functionOne 仅仅是一个赋予它的匿名函数的变量,而 functionTwo 实际上是一个命名函数。呼叫 .toString() 两者都看不出来。在某些您希望以编程方式获取函数名称的情况下,这很重要。 - Jason Bunting
有两种不同。第一个是 function expression 第二个是 function declaration。您可以在这里阅读更多相关主题: javascriptweblog.wordpress.com/2010/07/06/... - Michal Kuklis
@Greg关于解析时间与运行时间的答案部分是不正确的。在JavaScript中,函数声明不是在分析时定义的,而是在运行时定义的。过程如下:解析源代码 - >评估JavaScript程序 - >初始化全局执行上下文 - >执行声明绑定实例化。在此过程中,函数声明被实例化(参见第5步) 第10.5章)。 - Šime Vidas
这种现象的术语称为提升。 - Colin Pear
这个答案对尤金来说没什么。它与解析时间与运行时语句相比具有误导性。 - Griffin


首先,我想纠正格雷格: 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



我指的是RoBorg,但他无处可寻。简单:RoBorg ===格雷格。这就是历史可以在互联网时代重写的方式。 ;-) - Eugene Lazutkin
var xyz = function abc(){}; console.log(xyz === abc);我测试的所有浏览器(Safari 4,Firefox 3.5.5,Opera 10.10)都给了我“Undefined variable:abc”。 - NVI
总的来说,我认为这篇文章很好地解释了利用函数声明的差异和优势。我同意不同意将函数表达式赋值用于变量的好处,特别是因为“好处”似乎是声明一个全局实体的提议......并且每个人都知道你不应该混淆全局命名空间, 对? ;-) - natlee75
使用命名函数的一个重要原因是因为调试器可以使用该名称来帮助您理解调用堆栈或堆栈跟踪。当你看到调用堆栈并看到“匿名函数”10级深度时,它很糟糕... - goat
@Antimony函数声明与块不同。这应该更好地解释: stackoverflow.com/questions/17409945/... - Cypher


这是创建函数的标准表单的纲要: (最初是针对另一个问题编写的,但在被移入规范问题后进行了调整。)

条款:

快速清单:

  • 功能声明

  • “匿名” 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引擎应该做的事情 tryifswitchwhile等等,像这样:

if (someCondition) {
    function foo() {    // <===== HERE THERE
    }                   // <===== BE DRAGONS
}

因为他们被处理了 之前 一步一步的代码运行,知道当他们在一个控制结构时该怎么做是很棘手的。

虽然这样做不是 规定 直到ES2015,这是一个 允许的延期 支持块中的函数声明。不幸的是(并且不可避免地),不同的引擎做了不同的事情。

从ES2015开始,规范说明了该怎么做。事实上,它提供了三个单独的事情:

  1. 如果处于松散模式  在Web浏览器上,JavaScript引擎应该做一件事
  2. 如果在Web浏览器上处于松散模式,则JavaScript引擎应该执行其他操作
  3. 如果在 严格 模式(浏览器与否),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.definePropertyObject.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() 呼叫?这是一个功能。

关于箭头功能的一些事情:

  1. 他们没有自己的 this。相反,他们 关闭 该 this 他们被定义的背景。 (他们也关闭了 arguments 并且,相关的, super。)这意味着 this 在他们内部是相同的 this 他们在哪里创建,不能改变。

  2. 正如您已经注意到上述内容,您不使用关键字 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



然后是这个名字 w 简直被忽略了? - BiAiB
@PellePenna:函数名称对很多东西很有用。我视图中的两个biggies是递归,并且函数的名称显示在调用堆栈,异常跟踪等中。 - T.J. Crowder
这应该是公认的答案。它比上面的更新。 - Chaim Eliyah
@ChaimEliyah - “接受并不意味着它是最好的答案,它只是意味着它适用于那个问过的人。” 资源 - ScrapCode
@ A.R。:很正确。但有趣的是,正好在它之上说“最好的答案首先出现,以便它们总是很容易找到。”由于接受的答案首先出现在更高的投票答案上,因此巡回演出可能有些自相矛盾。 ;-)同样有点不准确,如果我们通过投票确定“最佳”(这是不可靠的,这只是我们得到的),“最佳”答案只会在您使用“投票”标签时首先显示 - 否则,首先是有效的答案,或者是最早的答案。 - T.J. Crowder


谈到全球背景,两者都有 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



我不知道在JavaScript中可以覆盖函数!此外,该解析订单对我来说是一个很大的卖点。我想我需要看看我是如何创建函数的。 - Xeoncross
+0到“Names function expressions demystified”文章,因为它是404ing。可能的镜子?: kangax.github.com/nfe - Mr_Chimp
@CMS很好。请记住,虽然我从未见过原版,所以我不知道这是一面镜子还是另一篇同名的文章! - Mr_Chimp
@Mr_Chimp我很确定它是,thewaybackmachine说它在爬行时得到302并且重定向到你提供的链接。 - John


您在那里发布的两个代码片段几乎在所有目的下都会以相同的方式运行。

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

随着第二个变种(function functionTwo()),该函数可用于在声明函数的上方运行的代码。

这是因为对于第一个变体,函数被赋值给变量 foo 在运行时。在第二个中,该功能被分配给该标识符, foo,在解析时。

更多技术信息

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

  1. 你的第一个片段显示了一个 功能表达。这涉及到使用 “功能”操作员 创建一个函数 - 该运算符的结果可以存储在任何变量或对象属性中。函数表达式就是这样强大的。函数表达式通常称为“匿名函数”,因为它不必具有名称,
  2. 你的第二个例子是 功能声明。这使用了 “功能”声明 创建一个功能。该函数在分析时可用,并且可以在该范围内的任何位置调用。您以后仍可以将其存储在变量或对象属性中。
  3. 定义函数的第三种方法是 “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



HI suhail感谢关于功能主题的明确信息。现在我的问题是,哪一个将是声明层次结构中的第一个声明,无论是变量声明(functionOne)还是函数声明(functionTwo)? - Sharathi RB


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

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

(function(){
    var exports = {};

    function privateUtil() {
            ...
    }

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

    return exports;
})();

使用此模式,您的公共函数将全部使用赋值,而您的私有函数使用声明。

(另请注意,赋值在语句后应该使用分号,而声明禁止它。)


83
2018-03-03 19:19



yuiblog.com/blog/2007/06/12/module-pattern 就我所知,它是模块模式的原始参考。 (虽然那篇文章使用了 var foo = function(){...} 语法甚至是私有变量。 - Sean McMillan
实际上,在一些旧版本的IE中,这并不完全正确。 (function window.onload() {} 是一件事。) - Ry-♦


何时优先考虑第一种方法到第二种方法的说明是当您需要避免覆盖函数的先前定义时。

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

,这个定义 myfunction 将覆盖任何先前的定义,因为它将在分析时完成。

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

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


68
2018-03-29 13:26



这个例子很好,接近完美,但可以改进。更好的例子是定义 var myFunc = null; 在循环之外,或在if / elseif / else块之外。然后,您可以有条件地为同一个变量分配不同的函数。在JS中,将缺失值分配给null,然后分配给undefined是一个更好的约定。因此,您应该首先将myFunction声明为null,然后在条件上稍后分配它。 - Alexander Mills


一个重要原因是添加一个且只有一个变量作为命名空间的“根”...

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

}

要么

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

命名空间有很多技巧。随着大量JavaScript模块的出现,它变得越来越重要。

另见 如何在JavaScript中声明命名空间?


55
2017-08-08 19:44



似乎这个答案从另一个问题和措辞中合并到了这个问题中 威力 似乎有点无关 这个 题。你会考虑编辑答案,因此它似乎更专注于这个问题吗? (重申一下;这根本不是你的错......只是合并问题的副作用)。你也可以删除它,我想你会保持你的声誉。或者你可以离开它;因为它已经老了,所以可能没什么大不了的。 - Andrew Barber