javascript:var functionName = function(){} 与 function functionName(){}的区别是什么

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

前面的开发人员使用了两种声明函数的方法,我不知道这背后是否有原因。

这两种方法是:

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

使用这两种不同方法的原因是什么?每种方法的优缺点是什么?有没有一种方法可以用另一种方法做不到的?


不同之处在于functionOne是一个函数表达式,因此只在到达该行时定义,而functionTwo是一个函数声明,并且在执行其周围的函数或脚本时定义(由于提升)。

例如,一个函数表达式:

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(); }
}

上面实际上定义了functionThree,而不考虑test的值—除非use strict有效,否则在这种情况下,它只会引发一个错误。


首先我想纠正格雷格:function abc(){}也是作用域—名称abc在遇到此定义的范围中定义。例子:

1
2
3
4
5
function xyz(){
  function abc(){};
  // abc is defined here...
}
// ...but not here

其次,这两种风格可以结合:

1
var xyz = function abc(){};

xyz将像往常一样被定义,abc在除internetexplorer和mdash之外的所有浏览器中都未定义;不要依赖于它的定义。但它将在体内定义:

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;

在这种情况下,xyzabc都是同一个对象的别名:

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);
  // ...
}

在上面的示例中,我们可以对外部名称执行相同的操作,但是它太笨重(而且速度更慢)。

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

实际上,JavaScript对这两个语句的处理是不同的。这是一个函数声明:

1
function abc(){}

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

此外,委员会通过了一项return声明:

1
2
3
4
// We can call it here
abc(); // Works
return;
function abc(){}

这是一个函数表达式:

1
var xyz = function(){};

xyz从赋值点定义:

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(){};

我知道我在范围链的任何地方都没有定义abc的情况下全局地定义了它。即使在eval()中使用,这种类型的定义也是有弹性的。而定义

1
function abc(){};

取决于上下文,可能会让您猜测它实际上是在哪里定义的,特别是在eval() —答案是:这取决于浏览器。


下面是创建函数的标准表单的概要:(最初是为另一个问题编写的,但在转入规范问题后进行了调整)。

术语:

ES5: ECMAScript第五版,2009ES2015: ECMAScript 2015(也称为"ES6")

快速列表:

函数声明

"匿名"function表达式(尽管有这个术语,但有时会创建带有名称的函数)

名为function表达式

访问器函数初始化器(ES5+)

箭头函数表达式(ES2015+)(与匿名函数表达式一样,它不涉及显式名称,但可以使用名称创建函数)

对象初始化器中的方法声明(ES2015+)

class (ES2015+)中的构造函数和方法声明

函数声明

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

1
2
3
function x() {
    console.log('x');
}

函数声明就是声明;它不是一个语句或表达式。因此,您不会在后面加上;(尽管这样做是无害的)。

函数声明是在执行进入它所出现的上下文时处理的,然后才执行任何一步一步的代码。它创建的函数被赋予一个适当的名称(在上面的例子中是x),这个名称被放在声明出现的范围中。

因为在相同的上下文中,它是在任何一步一步的代码之前处理的,所以您可以这样做:

1
2
3
4
x(); // Works even though it's above the declaration
function x() {
    console.log('x');
}

直到2015年,规范才涵盖了JavaScript引擎应该做什么,如果你把一个函数声明放在一个控件结构里面,比如tryifswitchwhile等等,就像这样:

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)

"匿名"

function表达式

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

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

与所有表达式一样,在逐步执行代码时,它将被计算。

在ES5中,它创建的函数没有名称(它是匿名的)。在ES2015中,如果可能的话,可以通过上下文推断来为函数分配一个名称。在上面的例子中,名称应该是y。当函数是属性初始化器的值时,也会执行类似的操作。(有关这种情况何时发生和规则的详细信息,请在规范中搜索SetFunctionName。到处都是。)

function表达式

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

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

它创建的函数有一个专有名称(在本例中为w)。与所有表达式一样,在逐步执行代码时将对其求值。函数名不添加到表达式出现的范围中;名称在函数本身的作用域内:

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"

注意,当我使用这个函数时,我没有使用()!这是因为它是属性的访问器函数。我们以正常的方式获取和设置属性,但是在后台,函数被调用。

您还可以使用Object.definePropertyObject.definePropertiesObject.create的第二个不太为人所知的参数来创建访问器函数。

箭头函数表达式(ES2015+)

ES2015给我们带来了arrow函数。这里有一个例子:

1
2
3
var a = [1, 2, 3];
var b = a.map(n => n * 2);
console.log(b.join(",")); // 2, 4, 6

看到隐藏在map()调用中的n => n * 2东西了吗?这是一个函数。

关于箭头函数有几点:

它们没有自己的this。相反,它们在定义它们的上下文的this上关闭。(它们也以argumentssuper结束。)这意味着其中的this与创建它们的this相同,并且不能更改。

正如您在上面所注意到的,您没有使用关键字function;相反,您使用=>

上面的n => n * 2示例是它们的一种形式。如果有多个参数要传递函数,则使用parens:

1
2
3
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语句和末尾的FunctionDeclaration将在全局对象上创建一个不可删除的属性,但是它们的值都可以被覆盖。

这两种方法的细微差别在于,当变量实例化过程运行时(在实际代码执行之前),所有用var声明的标识符都将用undefined初始化,而FunctionDeclaration使用的标识符将从那时起可用,例如:

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'

bar FunctionExpression的分配将一直执行到运行时。

FunctionDeclaration创建的全局属性可以像变量值一样被覆盖,没有任何问题,例如:

1
2
 function test () {}
 test = null;

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

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

如果使用赋值,而不使用var语句,则如果在范围链中没有找到引用的标识符,那么它将成为全局对象的可删除属性。

此外,未声明的赋值会在严格模式下对ECMAScript 5抛出一个ReferenceError

一个必须阅读:

命名函数表达式被解密

注意:这个答案与另一个问题合并了,在这个问题中,OP的主要疑问和误解是,用FunctionDeclaration声明的标识符不能被覆盖,而实际情况并非如此。


您在这里发布的两个代码片段几乎在所有目的上都具有相同的行为。

但是,行为上的不同之处在于,对于第一个变量(var functionOne = function() {}),该函数只能在代码中的那个点之后调用。

对于第二个变体(function functionTwo()),函数可用于在声明函数的地方运行的代码。

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

更多的技术信息

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

第一个代码片段显示了一个函数表达式。这涉及到使用"函数"操作符创建函数——该操作符的结果可以存储在任何变量或对象属性中。函数表达式就是这样强大的。函数表达式通常称为"匿名函数",因为它不必有名称,第二个例子是函数声明。它使用"function"语句来创建一个函数。函数在解析时可用,并且可以在该作用域中的任何位置调用。稍后您仍然可以将其存储在变量或对象属性中。定义函数的第三种方法是"function()"构造函数,这在您最初的文章中没有显示。不建议使用它,因为它的工作方式与eval()相同,后者有其问题。


对格雷格的回答有更好的解释

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
    }
}

myfunction的这个定义将覆盖以前的任何定义,因为它将在解析时执行。

1
2
3
4
5
if (condition){
    var myfunction = function (){
        // Some code
    }
}

仅当遇到condition时才执行正确的定义myfunction的工作。


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

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();

您可以使用jsperf Test Runner在不同的浏览器上检查函数声明、表达式测试

构造函数类:使用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 &amp; 1 ? 'Odd' : 'Even'; };
console.log( fn(2) ); // Even
console.log( fn(3) ); // Odd


我加上我自己的答案,只是因为其他人已经完全涵盖了提升部分。

很长一段时间以来,我一直在思考哪种方式更好,多亏了http://jsperf.com,现在我知道了:)

enter image description here

函数声明更快,这才是web开发中真正重要的,对吗?,)


一旦绑定建立,分配给变量的函数声明和函数表达式的行为就会相同。

但是,在如何以及何时将函数对象与其变量关联上存在差异。这种差异是由于JavaScript中称为变量提升的机制造成的。

基本上,所有函数声明和变量声明都被提升到声明发生的函数的顶部(这就是为什么我们说JavaScript具有函数作用域)。

当悬挂功能声明时,功能体"如下"所以当函数体被求值时,变量会立刻绑定到函数对象。

当挂起变量声明时,初始化不会挂起跟在后面,却"落在后面"。变量初始化为undefined在函数体的开头,并将被赋值位于代码中原始位置的值。(实际上,它将在出现同名变量声明的每个位置分配一个值。)

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

一些例子…

1
2
3
4
5
6
var foo = 1;
function bar() {
  if (!foo) {
    var foo = 10 }
  return foo; }
bar() // 10

变量foo被提升到函数的顶部,初始化为undefined,因此!footrue,因此foo被赋值为10。在bar的作用域之外的foo不发挥任何作用,并且是不受影响的。

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

在这个例子中,a是由函数对象初始化的,函数对象是计算第二个函数声明的结果,然后赋值给4

1
2
3
4
5
6
7
var a = 1;
function b() {
  a = 10;
  return;
  function a() {}}
b();
a // 1

这里首先挂起函数声明,声明并初始化变量a。接下来,这个变量被赋值为10。换句话说:赋值不赋给外部变量a


第一个例子是函数声明:

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() {};

函数语句只是一个带有function值的var语句的简写。

所以

1
function foo() {};

扩大到

1
var foo = function foo() {};

进一步扩展到:

1
2
var foo = undefined;
foo = function foo() {};

它们都被吊到代码的顶端。

Screenshot from video


@EugeneLazutkin给出了一个例子,他将一个指定的函数命名为能够使用shortcut()作为自身的内部引用。John Resig给出了另一个例子——在他的高级Javascript学习教程中,复制一个递归函数分配给另一个对象。在这里,虽然给属性分配函数并不是严格意义上的问题,但我建议积极尝试教程——通过单击右上角的按钮运行代码,然后双击代码来编辑您喜欢的代码。

教程中的示例:yell()中的递归调用:

测试失败时,原始忍者对象被删除。(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();

那么one.constructor.name将不被定义。Function.name是不标准的,但支持Firefox、Chrome、其他webkit派生的浏览器和IE 9+。

1
2
3
4
function functionTwo() {
    // Some code
}
two = new functionTwo();

可以使用two.constructor.name以字符串的形式检索构造函数的名称。


第一个(函数doSomething(x))应该是对象符号的一部分。

第二个(var doSomething = function(x){ alert(x);})只是创建一个匿名函数并将其分配给一个变量doSomething。因此doSomething()将调用该函数。

您可能想知道什么是函数声明和函数表达式。

函数声明定义了一个命名函数变量,而不需要赋值变量。函数声明作为独立构造出现,不能嵌套在非函数块中。

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

下面列出了两种不同的函数声明之间的四个值得注意的比较。

函数的可用性(范围)

下面的工作是因为function add()的作用域是最近的块:

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;
}

下面的方法不起作用(因为var add=超种子了function add())。

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;
}

下面的方法不起作用,因为add是在使用之后声明的。

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;
}

(函数). name

函数的名称function thefuncname(){}在以这种方式声明时是funcname。

1
2
3
function foobar(a, b){}

console.log(foobar.name);

1
2
3
var a = function foobar(){};

console.log(a.name);

否则,如果函数声明为function(){}, function.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测试可以在这里找到。正如您所看到的,在本不应该存在差异的情况下,却存在明显的差异。如果您真的像我一样是个性能怪才,那么在尝试减少作用域中变量和函数的数量,特别是消除多态性(例如使用相同的变量存储两种不同类型)时,可能更值得您这样做。

变量可变性

当您使用var关键字声明一个变量时,您可以像这样为该变量重新分配一个不同的值。

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时,变量引用变成不可变的。这意味着我们不能给变量赋一个新值。但是,请注意,这并不会使变量的内容不可变:如果您执行const arr = [],那么您仍然可以执行arr[10] ="example"。只有像arr ="new value"arr = []这样的操作才会抛出错误,如下所示。

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);
})();

有趣的是,如果我们将变量声明为function funcName(){},那么该变量的不变性与用var声明它是一样的。

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);
})();

什么是"最近的块"

"最近的块"是最近的"函数"(包括异步函数、生成器函数和异步生成器函数)。然而,有趣的是,在非闭包块中,function functionName() {}的行为类似于var functionName = function() {}。观察。

正常var add=function(){}

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}

正常function add(){}

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;
    }
})();

语句(如ifelseforwhiletry/catch/finallyswitchdo/whilewith)

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;
    }
}

带有var add=function()的箭头函数

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;
    }
})();

带有function add()的箭头函数

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() {}


性能:

V8的新版本引入了几个底层优化,SpiderMonkey也是如此。

现在表达式和声明之间几乎没有区别。函数表达式现在似乎更快了。

Chrome 62.0.3202Chrome test

FireFox 55Firefox test

Chrome金丝雀63.0.3225Chrome Canary test

Anonymous function expressions appear to have better performance
against Named function expression.

火狐Firefox named_anonymousChrome金丝雀Chrome canary named_anonymousChrome named_anonymous


两者都是定义函数的不同方法。区别在于浏览器如何解释它们并将它们加载到执行上下文中。

第一种情况是函数表达式,它只在解释器到达这一行代码时加载。如果你像下面这样做,你会得到一个错误,函数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


这只是声明函数的两种可能的方法,第二种方法是在声明之前使用函数。


new Function()可用于在字符串中传递函数的主体。因此这可以用来创建动态函数。还在不执行脚本的情况下传递脚本。

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推荐的一般实践是使用函数表达式而不是在if语句中声明。从链接中的示例中可以看出,if语句中的函数声明在Chrome和Firefox上的行为相同,但在Safari上则不同。

免责声明:macOS不能运行Microsoft Edge,所以我无法验证。

//函数声明示例var ed ="foo";控制台。原木(" foo' name ${ed ?")"is":"is not