函数声明与函数表达式
在 JavaScript 中定义函数有多种方式:
- 函数声明
function name() {}
- 函数表达式
myFunction = function name() {}
- 函数生成器声明
function* name() {}
- 函数生成器表达式
myFunction = function* () {}
- 箭头函数表达式
() => {}
- Function 构造函数
new Function()
- 生成器函数的构造函数
new GeneratorFunction()
在这篇文章中,我们只讨论函数声明与函数表达式的区别。
大多数情况下,函数声明和函数表达式的作用和效果是相同的,也就是进行一般的调用。但它们之间有一些不同的地方,不同之处在于浏览器如何将它们加载到执行上下文中。
一个广为人知的区别是:函数声明存在函数提升,这主要是为了允许声明前调用:
function addTwo(x) {
return addOne(addOne(x))
} // 能够调用后面的addOne函数
addTwo(1)
function addOne(x) {
return x + 1
} // 想要调用addTwo也是可以的
而我们知道函数表达式是不存在提升的。
此外,由于初版 JavaScript 开发仓促,函数提升的机制导致了 var
声明的提升,这使得变量没有了块的限制。
在 ES6 发布之后,出现了一种新的定义方式,叫做简写方法:
const obj = {
func() {
// 简写方法
},
func2: function () {
// ES5常规函数表达式
},
}
这种形式也应用于 class
中:
class A {
constructor() {
// constructor本身就是简写方法
}
a() {
// a也是简写方法
}
}
注意我使用简写方法与常规函数表达式来区分它们,因为他们有许多区别,简写方法的特点如下:
- 不能作为构造函数
简写方法不能够作为构造函数:
const obj = {
a() {},
}
new obj.a() // Uncaught TypeError: obj.a is not a constructor
- 没有原型
简写方法没有自己的 prototype
:
const obj = {
a() {},
}
console.log(obj.a.prototype) // undefined
- 可以在继承其他原型时使用
super
:
const obj1 = {
method1() {
console.log('method 1')
},
}
const obj2 = {
method2() {
super.method1()
},
}
Object.setPrototypeOf(obj2, obj1)
obj2.method2() // logs "method 1"
为什么这两种写法会有这些差别呢?据大师贺师俊(TC39、W3C 成员)的回答,原因如下:
传统的 JS 函数承载了太多功能,既可以做构造器又可以做方法,还可以做普通函数,造成使用上的混乱。
因此 ES6 对这些功能进行了分解,分别对应:类构造器、类方法、箭头函数。