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

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

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

两种方法是:

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

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


不同之处在于它functionOne是一个函数表达式,所以只有在达到该行时才被定义,而functionTwo函数声明是一个函数声明,并且在其周围函数或脚本执行后立即定义(由于提升)。

例如,一个函数表达式:

// TypeError: undefined 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,该规范并没有涵盖,如果你把一个控制结构像内部函数声明中的JavaScript引擎应该做的事情tryifswitchwhile,等等,是这样的:

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

而且由于它们是逐步执行的代码运行之前处理的,因此知道在控制结构中要做什么时很困难。

尽管在ES2015之前没有指定它,但它是一个允许的扩展,用于支持块中的函数声明。不幸的是(不可避免地),不同的引擎做了不同的事情。

从ES2015开始,规范说明了要做什么。事实上,它有三个独立的事情要做:

  1. 如果在松散模式下不在 Web浏览器上,JavaScript引擎应该做一件事
  2. 如果在Web浏览器中处于松散模式,JavaScript引擎应该做其他事情
  3. 如果处于严格模式(浏览器与否),JavaScript引擎应该做另一件事

松散模式的规则很棘手,但在严格模式下,块中的函数声明很容易:它们在块的本地(它们具有块范围,这在ES2015中也是新的),并且它们被悬挂到顶部的块。所以:

"use strict";
if (someCondition) {
    foo();               // Works just fine
    function foo() {
    }
}
console.log(typeof foo); // "undefined" (`foo` is not in scope here
                         // because it's not in the same block)

“匿名” function表达

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

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

像所有的表达式一样,它是在代码的逐步执行过程中进行评估的。

在ES5中,这个创建的函数没有名字(它是匿名的)。在ES2015中,如果可能的话,通过从上下文推断它,函数被分配一个名称。在上面的例子中,名字是y。当函数是属性初始值设定项的值时,会做类似的事情。(有关何时发生这种情况的细节和规则,搜索SetFunctionName规范  -它似乎遍布的地方。)

命名的function表达

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

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

这个创建的函数有一个专有名称(w在这种情况下)。像所有表达式一样,这是在代码的逐步执行中达到时评估的。该函数的名称不会添加到表达式的范围中; 名称在函数内部范围:

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

请注意,NFE经常成为JavaScript实现的错误来源。例如,IE8和更早版本,完全不正确地处理NFE ,在两个不同的时间创建两个不同的功能。Safari的早期版本也有问题。好消息是当前版本的浏览器(IE9及更高版本,目前的Safari)不再有这些问题。(但截至撰写本文时,遗憾的是,IE8仍然被广泛使用,因此使用NFE和网络代码通常仍然存在问题。)

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

有时候功能可能在很大程度上被忽视; 访问器功能就是这种情况。这是一个例子:

var obj = {
    value: 0,
    get f() {
        return this.value;
    },
    set f(v) {
        this.value = v;
    }
};
console.log(obj.f);         // 0
console.log(typeof obj.f);  // "number"

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

您也可以使用Object.definePropertyObject.defineProperties和创建访问函数,并且不太知道第二个参数Object.create

箭头函数表达式(ES2015 +)

ES2015为我们带来了箭头功能。这里有一个例子:

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

看到那个n => n * 2藏在map()通话中的东西?这是一个功能。

有关箭头功能的几件事情:

  1. 他们没有自己的this。相反,他们关闭了this他们定义成背景。(他们也关闭了arguments,在相关的情况下)super。这意味着this它们与this创建它们的地方相同,并且不能改变。
  2. 正如你会注意到上述,你不使用关键字function; 相反,你使用=>

n => n * 2上面的例子是它们中的一种。如果你有多个参数来传递函数,你可以使用parens:

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

(请记住,Array#map将条目作为第一个参数,将索引作为第二个参数。)

在这两种情况下,函数的主体只是一个表达式; 该函数的返回值将自动成为该表达式的结果(您不使用显式return)。

如果你做的不仅仅是一个表达式,{}而且return正常情况下使用和明确的(如果你需要返回一个值):

var a = [
  {first: "Joe", last: "Bloggs"},
  {first: "Albert", last: "Bloggs"},
  {first: "Mary", last: "Albright"}
];
a = a.sort((a, b) => {
  var rv = a.last.localeCompare(b.last);
  if (rv === 0) {
    rv = a.first.localeCompare(b.first);
  }
  return rv;
});
console.log(JSON.stringify(a));

无版本{ ... }被称为具有表达式主体简洁主体的箭头函数。(另外:一个简明的箭头函数。){ ... }定义正文的那个是带有函数体的箭头函数。(另外:详细的箭头功能。)

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

ES2015允许声明一个引用函数的属性的简短形式; 它看起来像这样:

var o = {
    foo() {
    }
};

ES5和更早版本中的等价物将是:

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

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

ES2015为我们带来了class语法,包括声明的构造函数和方法:

class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    getFullName() {
        return this.firstName + " " + this.lastName;
    }
}

上面有两个函数声明:一个是获取名称的构造函数,Person另一个getFullName是分配给函数的函数声明Person.prototype


谈到全球环境,var声明和FunctionDeclaration最后都会在全局对象上创建一个不可删除的属性,但是两者的价值都可以被覆盖

两种方法之间的细微差别是,当变量实例化过程运行时(在实际代码执行之前),所有声明的标识符var都将被初始化undefined,并且FunctionDeclaration从那一刻开始将使用所使用的标识符,例如:

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

发生的分配bar FunctionExpression直到运行时间。

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

 function test () {}
 test = null;

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

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

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

此外,未声明的作业ReferenceError严格模式下投放ECMAScript 5 。

必须阅读:

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

添加评论

友情链接:蝴蝶教程