一、函数参数的用法
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 的指向问题。
戳我博客
章节目录
1、ES6中啥是块级作用域?运用在哪些地方?
2、ES6中使用解构赋值能带给我们什么?
3、ES6字符串扩展增加了哪些?
4、ES6对正则做了哪些扩展?
5、ES6数值多了哪些扩展?
6、ES6函数扩展(箭头函数)
7、ES6 数组给我们带来哪些操作便利?
8、ES6 对象扩展
9、Symbol 数据类型在 ES6 中起什么作用?
10、Map 和 Set 两数据结构在ES6的作用
11、ES6 中的Proxy 和 Reflect 到底是什么鬼?
12、从 Promise 开始踏入异步操作之旅
13、ES6 迭代器(Iterator)和 for...of循环使用方法
14、ES6 异步进阶第二步:Generator 函数
15、JavaScript 异步操作进阶第三步:async 函数
16、ES6 构造函数语法糖:class 类