💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
### 函数参数的默认值 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 ```