### 函数参数的默认值
ES6允许为函数的参数设置默认值,即直接写在参数定义的后面
```js
function log (x, y = 'World') {
console.log(x, y)
}
```
参数变量是默认声明的,所以不能用 `let` 或 `const` 再次声明。
#### 参数默认值的位置
通常情况下,定义了默认值的参数应该是函数的尾参数。
```js
function f (x, y = 1) {
// ...
}
```
#### 函数的 Length 属性
指定了默认值后,函数的 `length` 属性将返回没有指定默认值的参数个数,也就是说指定了默认值后,`length` 属性将失真
#### 作用域
一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域,等到初始化结束,这个作用域就会消失。
```js
var x = 1
function f (x, y = x) {
console.log(y)
}
f(2) // 2
```
### rest参数
ES6引入了 `rest` 参数(形式为 ...变量名),用于获取函数的多余参数,这样就不需要使用 `arguments` 对象了,`rest` 参数搭配的变量是一个数组,该变量将多余的参数放入其中。
```js
function add (...values) {
let sum = 0
for (var val of values) {
sum += val
}
return sum
}
add(2, 5, 3) // 10
```
`rest`参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量。
`rest`参数之后不能再有其他参数,否则会报错。函数的 `length` 属性不包括 `rest` 参数。
### 严格模式
从ES5开始,函数内部可以设定为严格模式
```js
function doSomething (a, b) {
'use strict'
// code
}
```
ES6规定只要函数参数使用了默认值、解构赋值或扩展运算符,那么函数内部就不能显式的设定为严格模式,否则会报错。
### name属性
函数的 `name` 属性返回该函数的函数名
ES6对这个属性做出了一些修改。如果将一个匿名哈数赋值给一个变量,ES5的 `name` 属性会返回空字符串,而ES6的 `name` 属性会返回实际的函数名。
```js
var f = function () {}
// ES5
f.name // ""
// ES6
f.name // "f"
```
### 箭头函数
ES6允许使用 "箭头" ( => ) 定义函数
```js
var f = v => v
```
#### 注意事项
- 函数体内的 `this` 对象就是定义时所在的对象,而不是使用时所在的对象
- 不可以当做构造函数,也就是不可以使用 `new` 命令,否则会抛出一个错误。
- 不可以使用 `arguments` 对象,该对象在函数体内不存在。如果要用,可以用 `rest` 参数代替。
- 不可以使用 `yield` 命令,因此箭头函数不能用作 `Generator` 函数。
#### 嵌套的箭头函数
箭头函数内部还可以再使用箭头函数
### 绑定 this
箭头函数可以绑定 `this` 对象,大大减少显式绑定 `this` 对象的写法 (`call`、`apply` 和 `bind`)。但箭头函数并非适合所有场合。ES7提出了 "函数绑定" 运算符,用来取代 `call`、`apply` 和 `bind` 调用,Babel已支持。
函数绑定运算符是一个并排的双冒号 (::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象作为上下文环境(即 `this` 对象)绑定到右边的函数上。
```js
foo::bar
// 等同于
bar.bind(foo)
foo::bar(...arguments)
// 等同于
bar.bind(foo, arguments)
```
### 尾调用优化
#### 什么是尾调用
尾调用是函数式编程的一个重要概念。它指的是某个函数的最后一步是调用另外一个函数。
```js
function f (x) {
return g(x) // 最后一步调用函数g
}
```
#### 尾调用优化
尾调用的特殊在于其调用位置。函数调用会在内存形成一个调用记录,又称为 "调用帧",保存着调用位置和内部变量等信息。如果函数A的内部调用函数B,那么函数A的调用帧上方还会形成函数B的调用帧,等B的调用结束后,将结果返回给了A,B的调用帧才会消失,如果B的内部还调用了函数C,那么就还有一个C的调用帧,以此类推,所有的调用帧形成一个调用栈。
尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息用不到了,直接用内层函数的调用帧取代外层函数的即可。
```js
function f () {
let m = 1
let n = 2
return g(m + n)
}
f()
// 等同于
function f () {
return g(3)
}
f()
// 等同于
g(3)
```
尾调用优化即只保留内层函数的调用帧,如果所有的函数都是尾调用,那么每次执行时调用帧只有一项,这将大大节省内存。
#### 尾递归
函数调用自身称为递归,如果尾调用自身就称为尾递归。递归非常耗内存,很容易发生栈溢出错误,对于尾递归来说,由于只存在一个调用帧,所以永远都不会发生 "栈溢出"
```js
function factorial (n) {
if (n === 1) return 1
return n * factorial(n - 1)
}
factorial(5) // 120
// 改写成尾递归
function factorial (n, total) {
if (n === 1) return total
return factorial(n - 1, n * total)
}
factorial(5, 1) // 120
```