var functionName = function(){} 与 function functionName(){}

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

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

两种方式是:

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

使用这两种不同方法的原因是什么,每种方法的利弊是什么?有什么方法可以用另一种方法不能完成的?

答案

区别在于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生效,在这种情况下,它只会引发错误。

首先,我想更正 Greg: 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 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()的情况下—答案是:它取决于浏览器。

这是创建函数的标准表单的摘要:( 本来是为另一个问题而写的,但是在移入规范问题后进行了修改。)

条款:

快速清单:

  • 功能声明

  • “匿名” 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
}

而且由于它们是运行分步代码之前进行处理的,所以要知道它们在控制结构中时该怎么做是很棘手的。

尽管直到 2015 年才指定执行此操作,但这是允许的扩展,以支持块中的函数声明。不幸的是(不可避免),不同的引擎做了不同的事情。

从 ES2015 开始,该规范说明了怎么做。实际上,它提供了三个独立的操作:

  1. 如果松散模式不能在 Web 浏览器,JavaScript 引擎是应该做的一件事
  2. 如果在网络浏览器上处于松散模式,则 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));

没有{ ... }的版本称为带有表达式主体简洁主体的箭头函数。 (也: 简洁的箭头函数。)带有{ ... }定义 body 的函数是带有函数 body的箭头函数。 (还: 详细的箭头功能。)

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

ES2015 允许使用一种简短形式来声明引用一个称为方法定义的函数的属性;它看起来像这样:

var o = {
    foo() {
    }
};

ES5 和更早版本中几乎相等的是:

var o = {
    foo: function foo() {
    }
};

区别(除了冗长)是方法可以使用super ,而函数不能。因此,例如,如果您有一个使用方法语法定义(例如) valueOf的对象,则它可以使用super.valueOf()来获取值Object.prototype.valueOf将返回(之前可能会对它进行其他操作),而 ES5 版本将不得不执行Object.prototype.valueOf.call(this)

这也意味着该方法引用了对其定义的对象,因此,如果该对象是临时的(例如,您将其作为源对象之一传递给Object.assign ),则方法语法可能意味着否则可能会对其进行垃圾回收(如果 JavaScript 引擎未检测到这种情况并在没有方法使用super情况下进行处理),则将该对象保留在内存中。

构造和方法声明在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的函数。

说到全局上下文,最后的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'

FunctionExpression表达式bar的分配一直进行到运行时。

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

function test () {}
 test = null;

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

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

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

另外,在严格模式下,未声明的分配会在 ECMAScript 5 上引发ReferenceError

必须阅读:

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

您在此处张贴的两个代码段几乎可以出于所有目的以相同的方式运行。

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

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

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

更多技术信息

JavaScript 具有三种定义函数的方式。

  1. 您的第一个代码片段显示了一个函数表达式 。这涉及使用“函数” 运算符创建函数 - 该运算符的结果可以存储在任何变量或对象属性中。这样函数表达式功能强大。函数表达式通常称为 “匿名函数”,因为它不必具有名称,
  2. 您的第二个示例是函数声明 。这使用“功能” 语句创建功能。该函数在解析时可用,并且可以在该范围内的任何位置调用。以后您仍然可以将其存储在变量或对象属性中。
  3. 定义函数的第三种方式是“Function()” 构造函数 ,该函数未在原始文章中显示。不建议使用此方法,因为它的工作方式与eval()相同,但存在问题。

格雷格答案的更好解释

functionTwo();
function functionTwo() {
}

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

因为:

JavaScript 解释器总是将函数声明和变量声明不可见地移动( hoisted )到其包含范围的顶部。函数参数和语言定义的名称显然已经存在。 本樱桃

这意味着这样的代码:

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

请注意,声明的赋值部分未悬挂。仅悬挂名称。

但是对于函数声明,整个函数体也将被提升

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

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

我经常用以下模式构建 JavaScript 模块:

(function(){
    var exports = {};

    function privateUtil() {
            ...
    }

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

    return exports;
})();

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

(还请注意,在声明之后,赋值应使用分号,而在声明中则禁止使用分号。)

当您需要避免覆盖函数的先前定义时,最好使用第一种方法而不是第二种方法。

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

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

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

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

一个重要的原因是要添加一个且仅一个变量作为名称空间的 “根”。

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

}

要么

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

有很多命名空间的技术。可用的 JavaScript 模块数量越来越多,这一点变得越来越重要。

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

提升 是 JavaScript 解释器将所有变量和函数声明移到当前作用域顶部的操作。

但是,仅悬挂实际的声明。通过将作业留在原处。

  • 页面内声明的变量 / 函数是全局的,可以在该页面的任何位置访问。
  • 在函数内部声明的变量 / 函数具有局部作用域。表示它们在功能主体(作用域)内部可用 / 访问,在功能主体外部不可用。

变量

Javascript 被称为松散类型语言。这意味着 Javascript 变量可以保存任何Data-Type 的值。 Javascript 会根据运行时提供的值 / 字面值自动更改变量类型。

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';
}
  • 页面内部声明的函数被提升到具有全局访问权限的页面顶部。
  • 在功能块内部声明的功能被提升到该块的顶部。
  • 函数的默认返回值为 “ undefined ”, 变量声明的默认值也为 “undefined”

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


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

ArrowFunction : ArrowParameters => ConciseBody

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