这是创建函数的标准表单的摘要:(本来是为另一个问题而写的,但是在移入规范问题后进行了修改。)
条款:
快速清单:
功能声明
“匿名” 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
}
而且由于它们是在运行分步代码之前进行处理的,所以要知道它们在控制结构中时该怎么做是很棘手的。
尽管直到2015 年才指定执行此操作,但这是允许的扩展,以支持块中的函数声明。不幸的是(不可避免),不同的引擎做了不同的事情。
从ES2015开始,该规范说明了怎么做。实际上,它提供了三个独立的操作:
- 如果松散模式不是在Web浏览器,JavaScript引擎是应该做的一件事
- 如果在网络浏览器上处于松散模式,则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与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.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
上面的示例是它们的一种形式。如果您有多个参数来传递函数,请使用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的箭头函数。(还:详细的箭头功能。)
对象初始化器中的方法声明(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
。