一、函数参数的用法
1、参数默认值的设置,与 ES5 对比
ES5 中设置参数默认值的写法
- function animal(name, type) {
- var name = name || 'yuan';
- var type = type || 'monkey';
- console.log(name, type);
- }
这个写法有个缺陷:参数传递进来的布尔值必须为 true,如果传入的值为空字符串或者 undefined 等这些转换为 false 的参数,则会影响结构。所以,ES6 为了弥补这个缺陷,做了如下设置:
- function animal(name = 'yuan', type = 'monkey') {
- console.log(name, type);
- }
2、传入参数类型对输出结果的影响
- function animal(name = 'yuan', type = 'monkey') {
- console.log(name, type);
- }
- animal(); // yuan monkey
- animal(name = 'dog'); // dog monkey
- animal(name = 'yuan', undefined); // yuan monkey
- animal(name = 'yuan', null); // yuan null
- animal(false, undefined); // false "monkey"
- animal(0, undefined); // 0 "monkey"
上面代码 4 中调取函数的方式,只有不传和传入 undefined 的会触发默认值,而传入其它值或者 null,则不会触发默认值。
3、注意参数放置位置
- // 错误写法
- function animal(name = 'yuan', type) {
- console.log(name, type);
- }
- // 正确写法
- function animal(name, type = 'monkey') {
- console.log(name, type);
- }
如上,在设置了默认值的参数后,就不需要在设置其他参数了,通常情况下,若有参数为默认值时,一般是放在尾参数位置。
4、参数默认值与解构赋值默认值的结合使用
对比下面两种写法
- // 第一种写法,参数默认值为空对象,解构赋值有具体的默认值
- function method1({x = 0, y = 0} = {}) {
- return [x, y];
- }
- // 第二种写法,参数默认值是一个具体的属性对象,而对象解构赋值没有设置默认值
- function method2({x, y} = {x: 0, y: 0}) {
- return [x, y];
- }
- // 函数没有参数时
- method1(); // [0, 0]
- method2(); // [0, 0]
- // x 和 y 都有值
- method1({x: 2, y: 3}); // [2, 3]
- method2({x: 2, y: 3}); // [2, 3]
- // x 有值, y 没有值
- method1({x: 2}); // [2, 0]
- method2({x: 2}); // [2, undefined]
- // x 没有值, y 有值
- method1({y: 2}); // [0, 2]
- method2({y: 2}); // [undefined, 2
- // x 和 y 都没有值
- method1({}); // [0, 0]
- method2({}); // [undefined, undefined]
5、使用参数默认值对函数 length 属性的影响
- (function (a) {}).length // 1
- (function (a = 5) {}).length // 0
- (function (a, b, c = 5) {}).length // 2
- (function(a, ...b) {} ).length // 1
由上面代码可以看出,函数的参数在指定默认值之后,函数的 length 属性会失真,返回的 length 值,是没有指定默认值的参数的个数,注意这里的 length 也不包括 rest 参数(如第二点介绍)的个数。
6、注意点
因函数参数是默认声明的,如果如果用 let 或 const 重新声明变量,会报错:
- function animal(name = "yuan") {
- let name = dog;
- console.log(name);
- }
- animal(); // 报错: Uncaught SyntaxError: Identifier 'name' has already been declared
二、rest 参数
案例 呈现
- // 求和,把结果赋值给 result
- function sum(result, ...values) {
- console.log(values); // [1, 2, 3, 4],这个变量返回的是一个数组
- values.forEach(function(value, index) {
- result += value;
- }) console.log(result);
- }
- sum(11, 1, 2, 3, 4, ); // 21
如上,rest 参数(3 个点 + 变量名)表示的是:获取函数多余的参数,且这个变量是一个数组。还有一点要注意的是 rest 参数必须是尾参数,后面不能加其他的参数,否则会报错:
- // 错误写法
- function sum(res, ...values, another) {
- console.log(values);
- }
- sum(); // 报错:Uncaught SyntaxError: Rest parameter must be last formal parameter
三、name 属性
ES6 中增加了函数的 name 属性
- const animal = function() {};
- animal.name; // "animal"
Function 构造函数会发的函数实例,name 属性的值为 "anonymous"
- (new Function).name; // "anonymous"
bind 返回的函数,name 属性值会加上 "bound" 前缀
- function animal() {};
- animal.bind({}).name; // "bound animal"
匿名函数的 bind 返回的值 "bound"
- (function () {}).bind({}).name; // "bound"
四、箭头函数
1、定义
用箭头 "=>" 来定义函数。
2、用法
对比
- // ES5 写法
- var sum = function(a) {
- return a;
- }
- // ES6 写法
- var sum = a = >a;
如上代码,在 ES6 中,第一个 a 表示函数参数,箭头 "=>" 后面的 a 表示函数体。
上述只是针对一个参数和函数体只有一条语句的写法,若函数参数的个数和函数体的语句超过 1 个要如何表示呢?如下:
- var sum = (a, b) => { return a + b};
- sum(1,4); // 5
如上,若函数的参数个数超过一个时,需要用圆括号 "()" 来代表参数,函数体的语句条数超过一条时,需要用大括号将它们括起来。
当函数体中返回的是对象时,我们需要将其用圆括号 "()" 括起来:
- var person = name => ({ name: 'yuan', type: 'monkey'});
3、使用注意点
(1)、函数体内的 this 指向,指向的是定义时所在的对象,而不是使用时所在的对象。
在箭头函数中 this 指向是固定的:
- function foo() {
- setTimeout( () => {
- console.log('id:', this.id)
- }, 100);
- }
- var id = 21;
- foo.call({ id: 42}); // 42
如上输出结果,此处的 this 指向并不是全局对象 window,因为在箭头函数中,this 总是指向函数定义生效时所在的对象,所以输出的结果是 42。
如果还不清楚箭头函数 this 指向的含义,我们可以这样通俗的理解:在 JavaScript 中每一个 function 都有一个独立的运行上下文,而箭头函数不是一个普通的 function ,没有自己的运行上下文。所以在箭头函数中写的 this,具体指的是包含这个箭头函数最近的 function 的上下文中的 this,如果没有最近的 function,this 指向的是全局。
(2)、不能当做构造函数用,即不能使用 new 命令,否则会报错
(3),、不可以使用 arguments 对象,该对象哎函数体内不存在。如果要用,使用 rest 参数代替
(4)、不能使用 yield 命令,因为箭头函数不能用作 Generator 函数
六、函数的尾调用
1、定义
尾调用是函数式编程的一个重要概念,是指某个函数的最后一步调用另一个函数。
- function f(x) {
- return g(x);
- }
函数 f 的最后一步是调用函数 g,这就是尾调用。
2、尾递归
函数调用自身叫做递归,如果尾调用自身就是尾递归。
- function factorial(n) {
- if(n === 1) return 1;
- return n * factorial(n - 1);
- }
- factorial(5); // 120
在 ES6 中只要使用尾递归,就不会发生栈溢出,相对节省内存。
3、尾递归改写
为了确保最后一步只调用自身,需要对尾递归函数进行改写,把所有用到的内部变量改写成函数的参数。
使用柯里化函数编程思想,将多参数的函数转换成单参数的形式:
- function curring(fn, n) {
- return function(m) {
- return fn.call(this, m, n);
- }
- }
- function tailFactorial(n, total) {
- if (n === 1) return total;
- return tailFactorial(n - 1, n * total);
- }
- const factorial = curring(tailFactorial, 1);
- factorial(5); // 120
采用 ES6 的函数默认值改写:
- function factorial(n, total = 1) {
- if(n === 1) return total;
- return factorial(n - 1, n * total);
- }
- factorial(5); // 120
在尾调用优化时,循环是可以用递归代替的,而一旦使用递归,就最好使用尾递归。
五、总结
1、本章需要掌握 ES6 中函数的表示方式,以及 rest 参数的使用。
2、本章重点:箭头函数的表示方法,以箭头函数使用的注意事项,特别是箭头函数中 this 的指向问题。
本章完,下一章数组的扩展。
来源: http://www.jianshu.com/p/456f66db3159