最近想到一个问题:为什么JavaScript定义一个函数有那么多种形式,是历史残留还是为了装逼?研究了一下发现还是有区别的。

JavaScript中定义一个方法主要可以分成两种形式:

  • 函数声明
  • 函数表达式

函数声明

声明体是最普通的,其他语言也相近的一种方式。

1
2
3
4
5
function func(a, b) {
return a + b
}

func(1, 2) // 3

不过,毕竟是「函数就是爸爸」的JavaScript!它有一个特性:函数提升。

函数提升:把所有函数提升到当前作用域上,从而做到了可以“未声明先调用”。

函数表达式

这可能是目前用的最多的声明方式,因为可以搭配箭头函数假装自己用的是函数式编程。

1
2
3
4
5
6
7
8
const func = function(a, b) {
return a + b
}

// 箭头函数版本
const funcA = (a, b) => a + b

func(1,2) // 3

但变量受作用域影响,所以constlet并没有变量提升的能力。

1
2
funcA(1) // ReferenceError: funcA is not defined
const funcA = a => a

燃鹅,这才刚刚开始……

匿名 & 具名

匿名就是上面那种,具名就是给函数本体再起一个名字。

1
const func = function funcName() {}

这样有什么区别?后面那个名字又不能用!答案还是有区别的。

首先有一个函数名推断,比如上面这个函数,那么func.name会返回funcName。如果匿名函数的话[fn].name会返回''。其次这个名字可以在函数内部使用。当然了指代的就是自己了。

1
2
3
4
5
6
7
8
const func = function funcName(a, b) {
return a < 0 ? b : funcName(a+1-b, a)
}

func(431, 151) // 281
funcName(431, 151) // ReferenceError: funcName is not defined
console.log(func.name) // funcName
typeof funcName === 'function' // false

所以这种方式最适合递归函数了。

函数表达式依然是个常/变量

把它当做一个“方程”来看吧,关键词已经决定了这个“量”会以什么形式存在,比如varlet是变量,const是常量,并且let还有作用域范围。

1
2
3
4
5
6
7
8
const funcA = () => console.log('A')
funcA() // A
funcA = () => console.log('AA') // TypeError: Assignment to constant variable.

var funcB = () => console.log('B')
funcB() // B
funcB = () => console.log('BB')
funcB() // BB

题外话:那么就有个老生常谈的问题了,函数表达式按道理来讲是“灵活的”,那么下面这个函数会是什么结果?

1
2
3
4
5
6
7
8
function funcB() {
console.log('B')
}
funcB() // BB
function funcB() {
console.log('BB')
}
funcB() // BB

为什么?还是那句话:JavaScript世界中,函数是你爹!

箭头函数

又到了大家最最喜欢的箭头函数环节,现在还有谁是不愿意写箭头函数的?

1
2
const func = a => b => a + b
func(1)(2) // 3

在这篇文章:《关于JavaScript的this》已经被安排的明明白白了。

实际上现在看这篇文章也有点问题,年轻的时候牛批吹多了。总结箭头函数两个特点就是:

  • 不会创造上下文(自身无this)
  • 必然是个匿名函数
  • 没有 arguments

不是两个吗?怎么变成三个了?此时你可能会回去检查刚刚那句话,然后我现在悄悄告诉你,(第三点是送的)。

计算属性函数名

这个应该是在“对象”的环境中存在,毕竟对象可以指代很多种只要是对象的情况。

1
2
3
4
5
6
7
const object = {
['a' + 'b'](a, b) {
return a + b
}
}

object.ab(1, 3) // 3

这就实现了“函数名可以暂时不知道是什么”的情况,通过计算来得到这个函数。

Other

new Function

这是一个不知道哪里可以用得上但是就是可以用的方式:通过对象创建

1
2
const func = new Function('a', 'b', 'return a + b')
func(1, 2) // 3

函数参数初始值

可能有一定需求,但可能没有

1
2
3
4
5
function func(a = 1, b = 2) {
return a + b
}

func() // 3

可能我们更常用的有这些

1
2
3
const funcA = (obj = {}) => obj

const funcB = (arr = []) => arr

但JavaScript毕竟是「函数是第一公民」的语言,有机会会写到很多高阶函数。所以

1
const func = (fn = () => '💉💧🐮🍺') => fn.call(null)

凭什么函数作为参数就不能有初始值呢?当然是可以的呀!