函数声明与函数表达式

在 JavaScript 中定义函数有多种方式:

  1. 函数声明 function name() {}
  2. 函数表达式 myFunction = function name() {}
  3. 函数生成器声明 function* name() {}
  4. 函数生成器表达式 myFunction = function* () {}
  5. 箭头函数表达式 () => {}
  6. Function 构造函数 new Function()
  7. 生成器函数的构造函数 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也是简写方法
  }
}

注意我使用简写方法与常规函数表达式来区分它们,因为他们有许多区别,简写方法的特点如下:

  1. 不能作为构造函数

简写方法不能够作为构造函数:

const obj = {
  a() {},
}

new obj.a() // Uncaught TypeError: obj.a is not a constructor
  1. 没有原型

简写方法没有自己的 prototype

const obj = {
  a() {},
}

console.log(obj.a.prototype) // undefined
  1. 可以在继承其他原型时使用 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 对这些功能进行了分解,分别对应:类构造器、类方法、箭头函数。