函数的扩展
函数参数的默认值
-
基本用法
- 在ES6之前,不能直接为函数的参数指定默认值,只能采用变通的方法
- ES6允许为函数的参数设置默认值,即直接写在参数定义的后面。
- 除了简洁,ES6的写法还有两个好处:
- 阅读代码的人,可以立刻意识到哪些参数是可以省略的,不用查看函数体或文档;
- 有利于将来的代码优化,即使未来的版本彻底拿掉这个参数,也不会导致以前的代码 无法运行。
- 参数变量是默认声明的,所以不能用let或const再次声明
- 除了简洁,ES6的写法还有两个好处:
-
与解构赋值默认值结合使用
在使用了对象的解构赋值默认值的代码中,只有当函数的参数是一个对象 时,变量才会通过解构赋值而生成。如果函数调用时参数不是对象,变量就不会生成,从而报错。
-
参数默认值的位置
- 通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。 如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
- 如果没放在末尾,除非显式输入undefined; 如果传入undefined,将触发该参数等于默认值,null 则没有这个效果。
-
函数的length属性
指定了默认值以后,函数的 length 属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值 后,length 属性将失真。
- 为 length 属性的含义是,该函数预期传入的参数个数。某个参数指定默认值以后,预期传入的参数个数 就不包括这个参数了。同理,rest参数也不会计入length 属性。
-
作用域
如果参数默认值是一个变量,则该变量所处的作用域,与其他变量的作用域规则是一 样的,即先是当前函数的作用域,然后才是全局作用域。- 如果函数A的参数默认值是函数B ,由于函数的作用域是其声明时所在的作用域,那么函数B 的作用域不是函数A ,而是全局作用域
-
应用
- 利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
- 参数的默认值不是在定义时执行,而是在运行时执行(即如果参数已经赋值,默认值中的函数就不会运行)
rest参数
- 形式为“…变量“,用于获取函数的多余参数,这样就不需要使用arguments对象了。
- rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
- rest参数中的变量代表一个数组,所以数组特有的方法都可以用于这个变量。
- rest参数之后不能再有其他参数(即只能是后一个参数),否则会报错。
- 函数的length属性,不包括rest参数。
扩展运算符
扩展运算符(spread)是三个点(…)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列
- 该运算符主要用于函数调用
-
替代数组的apply方法
- JavaScript不提供求数组大元素的函数
- push 方法的参数不能是数组
-
扩展运算符的应用
- 合并数组
例:[…arr1, …arr2, …arr3] - 与解构赋值结合
将扩展运算符用于数组赋值,只能放在参数的后一位,否则会报错。 - 函数的返回值
JavaScript的函数只能返回一个值,如果需要返回多个值,只能返回数组或对象。扩展运算符提供了解决这个 问题的一种变通方法。 - 字符串
- 扩展运算符还可以将字符串转为真正的数组。
- 凡是涉及到操作32位Unicode字符的函数,最好都用扩展运算符改写,因为扩展运算符能够正确识别32位的Unicode字符。
- 类似数组的对象
任何类似数组的对象,都可以用扩展运算符转为真正的数组。 - Map和Set结构,Generator函数
如果对没有 iterator 接口的对象,使用扩展运算符,将会报错。
- 合并数组
name属性
函数的 name 属性,返回该函数的函数名。
- 如果将一个匿名函数赋值给一个变量,ES5的 name 属性,会返回空字符串,而ES6的 name属性会返回实际的函数名。
- 如果将一个具名函数赋值给一个变量,则ES5和ES6的 name返回这个具名函数原本的名字。
- Function 构造函数返回的函数实例,name 属性的值为“anonymous”。
- bind 返回的函数,name 属性值会加上“bound ”前缀
箭头函数
-
基本用法
var f = v => v; //箭头函数 var f = function(v){ //原函数 return v; }
- 如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。
- 如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
- 由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。
- 箭头函数可以与变量解构结合使用。
- 箭头函数使得表达更加简洁。
- 箭头函数的一个用处是简化回调函数。
-
使用注意点
- (1)函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
- this对象的指向是可变的,但是在箭头函数中,它是固定的
- 除了 this ,以下三个变量在箭头函数之中也是不存在的,指向外层函数的对应变 量:arguments 、super 、new.target 。
- ,由于箭头函数没有自己的this ,所以当然也就不能用call() 、apply() 、bind() 这些方法去改变 this 的指向。
- 长期以来,JavaScript语言的 this 对象一直是一个令人头痛的问题,在对象方法中使用 this ,必须非常小 心。箭头函数”绑定”this ,很大程度上解决了这个困扰。
- (2)不可以当作构造函数,也就是说,不可以使用 new 命令,否则会抛出一个错误。
- (3)不可以使用 arguments 对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。
- (4)不可以使用 yield 命令,因此箭头函数不能用作Generator函数。
- (1)函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。
-
嵌套的箭头函数
- 箭头函数内部,还可以再使用箭头函数。
- 箭头函数还有一个功能,就是可以很方便地改写λ演算。
函数绑定
函数绑定运算符是并排的两个双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左 边的对象,作为上下文环境(即this对象),绑定到右边的函数上面。
- 如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。
- 由于双冒号运算符返回的还是原对象,因此可以采用链式写法。
尾调用优化
指某个函数的 后一步是调用另一个函数。
- 尾调用不一定出现在函数尾部,只要是后一步操作即可
-
尾调用优化
- 尾调用之所以与其他调用不同,就在于它的特殊的调用位置。
- 这就叫做“尾调用优化”(Tail call optimization),即只保留内层函数的调用帧。如果所有函数都是尾调用, 那么完全可以做到每次执行时,调用帧只有一项,这将大大节省内存。这就是“尾调用优化”的意义。
- 注意,只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾 调用优化”。
-
尾递归
递归非常耗费内存,因为需要同时保存成千上百个调用帧,很容易发生“栈溢出”错误(stack overflow)。但 对于尾递归来说,由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
尾递归的实现,往往需要改写递归函数,确保后一步只调用自身。一旦使用递归,就好使用尾递归。
- 这样做的缺点就是不太直观,第一眼很难看出来,两个方法可以解决这个问题。
- 在尾递归函数之外,再提供一个正常形式的函数。
- 采用ES6的函数默认值。
函数参数的尾逗号
ES7有一个提案,允许函数的后一个参数有尾逗号(trailing comma)。目前,函数定义和调用时,都不允许有参数的尾逗号。
如果以后要在函数的定义之中添加参数,就势必还要添加一个逗号。这对版本管理系统来说,就会显示,添加 逗号的那一行也发生了变动。这看上去有点冗余,因此新提案允许定义和调用时,尾部直接有一个逗号。