var functionName = function(){} vs 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!");
};
首先,我想纠正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;
在这种情况下, xyz
和abc
都是同一对象的别名:
console.log(xyz === abc); // prints "true"
使用组合样式的一个令人信服的理由是函数对象的“名称”属性( 不受Internet Explorer支持 )。 基本上当你定义一个像
function abc(){};
console.log(abc.name); // prints "abc"
它的名字被自动分配。 但是当你定义它的时候
var abc = function(){};
console.log(abc.name); // prints ""
它的名字是空的 - 我们创建了一个匿名函数并将其分配给某个变量。
使用组合样式的另一个很好的理由是使用一个简短的内部名称来引用它自己,同时为外部用户提供一个长的非冲突名称:
// Assume really.long.external.scoped is {}
really.long.external.scoped.name = function shortcut(n){
// Let it call itself recursively:
shortcut(n - 1);
// ...
// Let it pass itself as a callback:
someFunction(shortcut);
// ...
}
在上面的例子中,我们可以用一个外部名称来做同样的事情,但它太笨拙了(而且速度较慢)。
(另一种引用自身的方法是使用arguments.callee
,它仍然相对较长,并且在严格模式下不受支持。)
深入研究,JavaScript对两种语句的处理都不同。 这是一个函数声明:
function abc(){}
这里的abc
在当前范围内的任何地方都有定义:
// We can call it here
abc(); // Works
// Yet, it is defined down there.
function abc(){}
// We can call it again
abc(); // Works
此外,它还通过一个return
声明悬挂起来:
// We can call it here
abc(); // Works
return;
function abc(){}
这是一个函数表达式:
var xyz = function(){};
这里的xyz
是从赋值的角度定义的:
// We can't call it here
xyz(); // UNDEFINED!!!
// Now it is defined
xyz = function(){}
// We can call it here
xyz(); // works
函数声明与函数表达式是Greg为什么会有区别的真正原因。
有趣的事实:
var xyz = function abc(){};
console.log(xyz.name); // Prints "abc"
就我个人而言,我更喜欢“函数表达式”声明,因为这样我可以控制可见性。 当我像这样定义函数时
var abc = function(){};
我知道我在本地定义了这个功能。 当我像这样定义函数时
abc = function(){};
我知道我在全球范围内定义了它,因为我没有在范围链中的任何地方定义abc
。 即使在eval()
内部使用,这种风格的定义也是有弹性的。 而定义
function abc(){};
取决于上下文,并可能让您猜测它实际定义的位置,特别是在eval()
的情况下 - 答案是:取决于浏览器。
以下是创建函数的标准表格的简要说明:(原本是为另一个问题编写的,但是在转入规范问题后进行了修改。)
条款:
快速列表:
函数声明
“匿名” 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引擎应该做什么,如果你把一个函数声明放在像try
, if
, switch
, while
等这样的控制结构中,像这样:
if (someCondition) {
function foo() { // <===== HERE THERE
} // <===== BE DRAGONS
}
而且由于它们是在逐步执行的代码运行之前进行处理的,因此知道在控制结构中要做什么时很困难。
尽管在ES2015之前没有指定它,但它是一个允许的扩展,用于支持块中的函数声明。 不幸的是(不可避免地),不同的引擎做了不同的事情。
从ES2015开始,规范说明了要做什么。 事实上,它有三个独立的事情要做:
松散模式的规则很棘手,但在严格模式下,块中的函数声明很容易:它们在块的本地(它们具有块范围,这在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.defineProperty
, Object.defineProperties
和Object.create
的较少已知的第二个参数创建存取器函数。
箭头函数表达式(ES2015 +)
ES2015为我们带来了箭头功能。 这里有一个例子:
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
。 (他们还关过arguments
,并在适当情况下super
)。这意味着, this
在它们是一样的this
产生它们在哪里,并且不能更改。
正如你将会注意到的那样,你不使用关键字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 functionName = function() {} vs function functionName() {}