这是创建函数的标准形式的纲要:(最初是为另一个问题编写的,但在移入规范问题后进行了调整。)
条款:
快速清单:
函数声明
“匿名”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 开始,规范说明了要做什么。事实上,它提供了三件独立的事情要做:
- 如果不是在 Web 浏览器上处于松散模式,JavaScript 引擎应该做一件事
- 如果在 Web 浏览器上处于松散模式,JavaScript 引擎应该做其他事情
- 如果在严格模式下(浏览器与否),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.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
看到n => n * 2
隐藏在map()
通话中的那个东西了吗?那是一个功能。
关于箭头函数的一些事情:
他们没有自己的this
。相反,他们关闭了在this
他们定义成背景。(它们也关闭,arguments
并且在相关的地方,super
。)这意味着它们的this
内部与this
它们的创建位置相同,并且不能更改。
正如您在上面注意到的那样,您不使用关键字function
; 相反,您使用=>
.
n => n * 2
上面的例子是其中的一种形式。如果您有多个参数来传递函数,请使用括号:
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的箭头函数。(另外:一个简洁的箭头函数。){ ... }
定义主体的箭头函数是一个带有函数 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;
}
}
上面有两个函数声明:一个用于构造函数,它获取 name Person
,另一个 for getFullName
,它是分配给 的函数Person.prototype
。