提问



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


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


这两种方式是:


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




function functionTwo() {
    // Some code
}


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

最佳参考


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


例如,一个函数表达式:




// TypeError: functionOne is not a function
functionOne();

var functionOne = function() {
  console.log("Hello!");
};



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


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


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


使用组合样式的一个令人信服的理由是功能对象的名称属性(不支持Internet 资源管理器)。基本上当你定义一个像这样的函数


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()—答案是:这取决于浏览器。

其它参考1


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


条款:



  • ES5 :ECMAScript第5版,2009

  • ES2015 :ECMAScript 2015(也称为ES6)



快速清单:[204] [205]



  • 功能声明

  • 匿名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之前,如果你将函数声明放在tryifswitchwhile等控制结构中,那么规范并没有涵盖JavaScript引擎应该做什么,等,像这样:


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—它出现遍布这个地方。)[206]


命名为function表达式



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


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


它创建的函数具有正确的名称(在这种情况下为w)。与所有表达式一样,在逐步执行代码时达到此值时进行评估。函数的名称​​ not 添加到表达式出现的作用域中;名称 在函数本身的范围内:


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


请注意,NFE经常成为JavaScript实现的错误来源。例如,IE8及更早版本完全错误地处理NFE,在两个不同的时间创建两个不同的函数。早期版本的Safari也存在问题。好消息是当前版本的浏览器(IE9及更高版本,当前的Safari)不再存在这些问题。(但在撰写本文时,遗憾的是,IE8仍然广泛使用,因此使用NFE代码进行网络编写一般来说仍然存在问题。)[207]


存取器功能初始化器(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.definePropertiesObject.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的函数。

其它参考2


说到全局上下文,最后var语句和FunctionDeclaration将在全局对象上创建不可删除的属性,但两者的值< em>可以被覆盖。


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


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


bar FunctionExpression的赋值发生在运行时间之前。


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


 function test () {}
 test = null;


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


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


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


此外,未声明的作业在严格模式下对ECMAScript 5投掷ReferenceError[209]


必读:



  • 命名函数表达式揭秘



注意:这个答案已经从另一个问题中合并,其中来自OP的主要疑问和误解是用FunctionDeclaration声明的标识符,不能被覆盖,但事实并非如此[210]

其它参考3


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


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


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


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


更多技术信息


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



  1. 您的第一个代码段显示函数表达式。这涉及使用function运算符来创建函数 - 该运算符的结果可以存储在任何变量或对象属性中。函数表达式就是这样强大的。函数表达式通常称为匿名函数,因为它不必具有名称

  2. 您的第二个示例是函数声明。这使用function语句来创建一个函数。该函数在分析时可用,并且可以在该范围内的任何位置调用。您稍后仍可以将其存储在变量或对象属性中。

  3. 定义函数的第三种方法是Function()构造函数,它未在原始帖子中显示。不推荐使用它,因为它的工作方式与eval()相同,后者存在问题。


其它参考4


对格雷格答案的更好解释


functionTwo();
function functionTwo() {
}


为什么没有错误?我们总是被告知表达式是从上到下执行的(??)


因为:




  函数声明和变量声明总是被JavaScript解释器无形地移动到其包含范围的顶部(hoisted。显然,功能参数和语言定义的名称已经存在。本樱桃[213]



这意味着代码如下:


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


请注意,声明的赋值部分未被提升。只有名字被悬挂。


但是在函数声明的情况下,整个函数体也将被提升:


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

其它参考5


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


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


(function(){
    var exports = {};

    function privateUtil() {
            ...
    }

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

    return exports;
})();


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


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

其它参考6


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





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


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





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


只有在满足condition时才能正确定义myfunction

其它参考7


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


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

}


要么


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


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


另请参阅如何在JavaScript中声明命名空间?

其它参考8


提升 是JavaScript解释器将所有变量和函数声明移动到当前范围顶部的操作。 [215]


但是,只有实际的声明才会被提升。将作业留在原处。



  • 在页面内声明的变量s/Function是全局的,可以访问该页面中的任何位置。

  • 变量s/函数内声明的函数具有局部范围。意味着它们在函数体(范围)内可用/访问,它们在函数体外不可用。



可变 [216]


Javascript被称为松散类型的语言。这意味着Javascript变量可以保存任何数据类型的值。 Javascript会自动根据运行时提供的值/文字来更改变量类型。 [217]


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





功能


function Identifier_opt ( FormalParameterList_opt ) { 
      FunctionBody | sequence of statements

      « return;  Default undefined
      « return 'some data';
}



  • 页面内声明的函数将被提升到具有全局访问权限的页面顶部。

  • 在功能块内声明的函数被提升到块的顶部。

  • 函数的默认返回值是未定义,变量声明默认值也是未定义[218] [219]


    Scope with respect to function-block global. 
    Scope with respect to page undefined | not available.
    



功能声明


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


功能表达


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


分配给变量的函数示例:


(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解释为


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 [220]检查不同浏览器的函数声明,表达式测试





ES5构造函数类:使用Function.prototype.bind创建的函数对象


JavaScript将函数视为第一类对象,因此作为对象,您可以为函数指定属性。


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引入了箭头函数:箭头函数表达式具有更短的语法,它们最适合非方法函数,并且它们不能用作构造函数。



  ArrowFunction : ArrowParameters => ConciseBody[222]


const fn = (item) => { return item & 1 ? 'Odd' : 'Even'; };
console.log( fn(2) ); // Even
console.log( fn(3) ); // Odd


其它参考9


我正在添加我自己的答案,因为其他人都彻底覆盖了吊装部件。


我很想知道现在哪种方式更好,现在感谢http://jsperf.com我知道:[223]





功能声明更快,这对于网络开发中真正重要的是什么?;)

其它参考10


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


但 和时与函数对象实际上与其变量关联时存在差异。这种差异是由于JavaScript中称为变量提升的机制造成的。


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



  • 当函数声明被提升时,函数体跟随
    所以当评估函数体时,变量将立即生效
    被绑定到一个函数对象。

  • 当提升变量声明时,初始化不
    跟随,但落后。变量初始化为
    undefined在函数体的开头,将分配
    代码中原始位置的值。 (实际上,它将在每个位置分配一个值,其中发生具有相同名称的变量的声明。)



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


一些例子...


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


变量foo被提升到函数的顶部,初始化为undefined,因此!footrue,因此foo被赋值为10bar范围外的foo不起作用,也不受影响。


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

其它参考11


第一个例子是函数声明:


function abc(){}


第二个例子是函数表达式:


var abc = function() {};


主要区别在于如何吊起(举起和宣布)。在第一个示例中,整个函数声明被提升。在第二个示例中,仅提升varabc,其值(函数)将是未定义的,函数本身仍保留在声明它的位置。


简而言之:


//this will work
abc(param);
function abc(){}

//this would fail
abc(param);
var abc = function() {}


要了解有关此主题的更多信息,我强烈建议您这样做
链路[224]

其它参考12


在代码维护成本方面,命名函数更为可取:



  • 独立于声明的地方(但仍受范围限制)。

  • 更能抵抗条件初始化等错误(如果需要,您仍然可以覆盖)。

  • 通过分配范围功能的本地功能,代码变得更具可读性。通常在范围内,首先是功能,然后是本地功能的声明。

  • 在调试器中,您将清楚地看到调用堆栈上的函数名称,而不是匿名/评估函数。



我怀疑命名函数的PROS会更多。被列为命名函数优势的是匿名函数的缺点。


从历史上看,匿名函数出现在JavaScript无法作为列出具有命名函数的成员的语言中:


{
    member:function() { /* How do I make "this.member" a named function? */
    }
}

其它参考13


我在代码中使用变量方法是出于一个非常具体的原因,其理论已经在上面以一种抽象的方式进行了介绍,但是一个例子可能会帮助像我这样的人,JavaScript专业知识有限。


我有代码,我需要运行160个独立设计的品牌。大多数代码都在共享文件中,但品牌特定的东西在一个单独的文件中,每个品牌一个。


有些品牌需要特定功能,有些则不需要。有时我必须添加新功能来执行新的品牌特定事物。我很乐意更改共享编码,但我不想更改所有160套品牌文件。


通过使用变量语法,我可以在共享代码中声明变量(本质上是一个函数指针),并分配一个普通的存根函数,或设置为null。


然后,需要特定功能实现的一个或两个品牌可以定义它们的函数版本,并在需要时将其分配给变量,其余的则不做任何事情。我可以在共享代码中执行之前测试null函数。


根据上面人们的评论,我认为也可以重新定义一个静态函数,但我认为变量解决方案很好而且清晰。

其它参考14


在计算机科学术语中,我们讨论匿名函数和命名函数。我认为最重要的区别是匿名函数没有绑定到名称,因此名称是匿名函数。在JavaScript中,它是在运行时动态声明的第一个类对象。


有关匿名函数和lambda演算的更多信息,维基百科是一个好的开始(http://en.wikipedia.org/wiki/Anonymous_function).[225]

其它参考15


格雷格的答案已经足够好了,但我还是想补充一点,我刚刚看到道格拉斯·克罗克福德的视频。 [227]


功能表达:


var foo = function foo() {};


功能声明:


function foo() {};


函数语句只是具有function值的var语句的简写。


所以


function foo() {};


扩展到


var foo = function foo() {};


其进一步扩展到:


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


它们都被提升到代码的顶部。




其它参考16


@EugeneLazutkin给出了一个例子,他将一个指定的函数命名为能够使用shortcut()作为自身的内部引用。 John Resig给出了另一个例子 - 在他的Learning Advanced Javascript教程中复制分配给另一个对象的递归函数。虽然将属性分配给属性并不是严格意义上的问题,但我建议主动尝试教程 - 通过单击右上角的按钮运行代码,然后双击代码进行编辑,以符合您的喜好。 [230] [231]


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


删除原始ninja对象时测试失败。 (第13页)[232]


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页)[233]


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." );

其它参考17


其他答案中没有提到的另一个区别是,如果您使用匿名函数


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


并将其用作构造函数


var one = new functionOne();


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





function functionTwo() {
    // Some code
}
two = new functionTwo();


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

其它参考18


如果您将使用这些函数来创建对象,您将获得:


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

其它参考19


我列出了以下差异:



  1. 函数声明可以放在代码中的任何位置。即使在定义出现在代码之前调用它,它也会在函数声明被提交到内存或以某种方式被提升之前执行,之后页面中的任何其他代码开始执行。


    看看下面的功能:


    function outerFunction() {
        function foo() {
           return 1;
        }
        return foo();
        function foo() {
           return 2;
        }
    }
    alert(outerFunction()); // Displays 2
    


    这是因为,在执行期间,它看起来像: -


    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
    


    函数表达式(如果在调用之前未定义)将导致错误。此外,这里函数定义本身不会像函数声明一样移动到顶部或提交到内存。但是我们分配函数的变量会被提升并且未定义被赋值给它。


    使用函数表达式的相同函数


    function outerFunction() {
        var foo = function() {
           return 1;
        }
        return foo();
        var foo = function() {
           return 2;
        }
    }
    alert(outerFunction()); // Displays 1
    


    这是因为在执行期间,它看起来像:


    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
    

  2. if 等非功能块中编写函数声明是不安全的,因为它们不可访问。


    if (test) {
        function x() { doSomething(); }
    }
    

  3. 如下所示的命名函数表达式可能无法在版本9之前的Internet浏览器浏览器中使用。


    var today = function today() {return new Date()}
    


其它参考20


第一个(函数doSomething(x))应该是对象表示法的一部分。


第二个(var doSomething = function(x){ alert(x);})只是创建一个匿名函数并将其赋值给变量doSomething。所以doSomething()会调用该函数。


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


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


function foo() {
    return 3;
}



  ECMA 5(13.0)将语法定义为

  函数标识符(FormalParameterList opt ){FunctionBody}



在上述条件中,函数名称在其范围内以及其父级的范围内可见(否则将无法访问)。


并在函数表达式中


函数表达式将函数定义为较大表达式语法(通常是变量赋值)的一部分。通过函数表达式定义的函数可以是命名的或匿名的。函数表达式不应以function开头。


// 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)将语法定义为

  函数标识符 opt (FormalParameterList opt ){FunctionBody}


其它参考21


鉴于命名函数显示在堆栈跟踪参数,现代JavaScript引擎实际上非常能够表示匿名函数。


在撰写本文时,V8,SpiderMonkey,Chakra和Nitro总是通过名称来引用命名函数。它们几乎总是通过标识符引用匿名函数(如果有的话)。


SpiderMonkey可以找出从另一个函数返回的匿名函数的名称。其余的不能。


如果你真的,真的希望你的迭代器和成功回调显示在跟踪中,你可以命名那些......


[].forEach(function iterator() {});


但在大多数情况下,不值得强调。


线束(小提琴)



'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 + '\n' +
                           'Trace:\n' +
                           error.stack);
        // Need to manually log the error object in Nitro.
    }

}, []).join('\n\n'));


V8



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的



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


脉轮



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)


硝基



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

其它参考22


在JavaScript中有两种创建函数的方法:[234]



  1. 功能声明:


    function fn(){
      console.log("Hello");
    }
    fn();
    


    这是非常基本的,不言自明的,在C语言系列中以多种语言和标准使用。我们声明了一个函数定义它并通过调用它来执行它。


    您应该知道的是,函数实际上是JavaScript中的对象;在内部,我们为上面的函数创建了一个对象,并给它一个名为fn的名称,或者对象的引用存储在fn中。函数是JavaScript中的对象;函数实例实际上是一个对象实例。

  2. 功能表达:


    var fn=function(){
      console.log("Hello");
    }
    fn();
    


    JavaScript具有一流的功能,即创建一个函数并将其分配给变量,就像创建字符串或数字并将其分配给变量一样。这里,fn变量被赋值给一个函数。这个概念的原因是函数是JavaScript中的对象; fn指向上述函数的对象实例。我们初始化了一个函数并将其分配给变量。它没有执行该功能并分配结果。



参考: JavaScript函数声明语法:var fn=function(){} vs function fn(){} [235]

其它参考23


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


第一种情况是函数表达式,仅在解释器到达该行代码时才加载。因此,如果您执行以下操作,则会出现 functionOne不是函数的错误。


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


原因是在第一行没有为functionOne赋值,因此它是未定义的。我们试图将其称为函数,因此我们遇到了错误。


在第二行,我们将一个匿名函数的引用分配给functionOne。


第二种情况是在执行任何代码之前加载的函数声明。因此,如果您喜欢以下内容,则在代码执行之前加载声明时不会出现任何错误。


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

其它参考24


关于效果:


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


表达式和声明之间现在几乎没有区别。
函数表达式现在看起来更快。[236]


Chrome 62.0.3202
[237]


FireFox 55
[238]


Chrome Canary 63.0.3225
[239]


点击



  Anonymous函数表达式似乎具有更好的性能
  反对Named函数表达式。[240]



点击


火狐

Chrome Canary


[241] [242] [243]

其它参考25


它们非常相似,只有一些小差异,第一个是分配给匿名函数的变量(函数声明),第二个是在JavaScript中创建函数的常规方法(匿名函数声明),两者都有用法,缺点和优点:


1。功能表达



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



  函数表达式将函数定义为较大的函数的一部分
  表达式语法(通常是变量赋值)。功能
  通过函数定义表达式可以命名或匿名。功能
  表达式不能以function开头(因此括号
  围绕下面的自我调用示例)。



将一个变量分配给一个函数,意味着没有提升,因为我们知道JavaScript中的函数可以提升,意味着它们可以在声明之前被调用,而变量需要在访问它们之前声明,所以在这种情况下意味着我们不能在声明它之前访问函数,也可以是你编写函数的一种方式,对于返回另一个函数的函数,这种声明可能有意义,同样在ECMA6和上面你可以将它分配给一个可以用来调用匿名函数的箭头函数,这种声明方式是在JavaScript中创建构造函数的更好方法。


2。功能声明



function functionTwo() {
    // Some code
}



  函数声明定义了一个没有的命名函数变量
  需要变量赋值。函数声明以
  独立构造,不能嵌套在非功能块中。
  将它们视为变量声明的兄弟是有帮助的。
  就像变量声明必须以var,Function开头一样
  声明必须以function开头。



这是在JavaScript中调用函数的常规方法,这个函数可以在你甚至声明它之前调用它,因为在JavaScript中所有函数都被提升,但是如果你有使用严格,这就不会像预期的那样升级,它会很好调用行中不大的所有普通函数的方法,也不是构造函数。


此外,如果您需要有关如何在JavaScript中提升的更多信息,请访问以下链接:


https://developer.mozilla.org/en-US/docs/Glossary/Hoisting[244]

其它参考26


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



  1. 功能的可用性(范围)



以下工作原因是function add()的范围限定为最近的块:




try {
  console.log("Success: ", add(1, 1));
} catch(e) {
  console.log("ERROR: " + e);
}

function add(a, b){
  return a + b;
}



这只是声明函数的两种可能方式,在第二种方式中,您可以在声明之前使用该函数。

其它参考27


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


var func = new Function("x", "y", "return x*y;");
function secondFunction(){
   var result;
   result = func(10,20);
   console.log ( result );
}

secondFunction()