ES6中,函数新增了几个功能,特别是箭头函数和参数默认值,在实际项目中经常用到
文章主要参考阮一峰老师的 ECMAScript 6入门,站在巨人的肩膀上看世界…
函数参数的默认值
简单来说,就是在函数的形参中可以添加默认值了,举个例子看了就明白了
1 2 3 4 5 6
| function add(a = 1){ var num = 1 + a; console.log(num) } add() // 2 add(5) // 6
|
没有数值的时候就使用默认值,使用起来便于优化,代码一目了然.
需要注意的是,这个形参是函数默认声明的,在函数体中,不能用let或const再次声明,否则会报错。
1 2 3 4
| function foo(x = 5) { let x = 1; // error const x = 2; // error }
|
使用参数默认值时,函数不能有同名参数。
1 2 3 4 5 6 7 8 9
| // 不报错 function foo(x, x, y) { // ... }
// 报错 function foo(x, x, y = 1) { // ... }
|
注意!!!!参数默认值不是传值的,而是每次都重新计算默认值表达式的值。也就是说,参数默认值是惰性求值的。
1 2 3 4 5 6 7 8 9
| let x = 99; function foo(p = x + 1) { console.log(p); }
foo() // 100
x = 100; foo() // 101
|
如果传入undefined,将触发该参数等于默认值,null则没有这个效果。
1 2 3 4 5
| function foo(x = 5, y = 6) { console.log(x, y); }
foo(undefined, null) // 5 null
|
与解构赋值默认值结合使用
1 2 3 4 5 6 7 8 9 10 11 12
| function foo({x, y = 5}) { console.log(x, y); }
// 个人的理解是类似于参数为一个对象,所以需要传入一个对象 // 就算x是有值的,也会报错,假如参数是 {x = 2, y = 5},不传{}一样是会报错的
foo({}) // undefined 5 foo({x: 1}) // 1 5 foo({x: 1, y: 2}) // 1 2 foo() // TypeError: Cannot read property 'x' of undefined
|
但是通过提供函数参数的默认值,就可以避免这种情况
1 2 3 4 5
| function foo({x, y = 5} = {}) { console.log(x, y); }
foo() // undefined 5
|
再来看一个例子,来弄清楚函数形参中的解构赋值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| // 写法一 function m1({x = 0, y = 0} = {}) { return [x, y]; }
// 写法二 function m2({x, y} = { x: 0, y: 0 }) { return [x, y]; }
// 函数没有参数的情况 m1() // [0, 0] m2() // [0, 0]
// x 和 y 都有值的情况 m1({x: 3, y: 8}) // [3, 8] m2({x: 3, y: 8}) // [3, 8]
// x 有值,y 无值的情况 m1({x: 3}) // [3, 0] m2({x: 3}) // [3, undefined]
// x 和 y 都无值的情况 m1({}) // [0, 0]; m2({}) // [undefined, undefined]
m1({z: 3}) // [0, 0] m2({z: 3}) // [undefined, undefined]
|
参数默认值的位置
通常情况下,定义了默认值的参数,应该是函数的尾参数。因为这样比较容易看出来,到底省略了哪些参数。如果非尾部的参数设置默认值,实际上这个参数是没法省略的。
举几个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| // 例一 function f(x = 1, y) { return [x, y]; }
f() // [1, undefined] f(2) // [2, undefined]) f(, 1) // 报错 f(undefined, 1) // [1, 1]
// 例二 function f(x, y = 5, z) { return [x, y, z]; }
f() // [undefined, 5, undefined] f(1) // [1, 5, undefined] f(1, ,2) // 报错 f(1, undefined, 2) // [1, 5, 2]
|
函数参数的 length 属性
指定了默认值以后,函数的length属性,将返回没有指定默认值的参数个数。也就是说,指定了默认值后,length属性将失真。
1 2 3
| (function (a) {}).length // 1 (function (a = 5) {}).length // 0 (function (a, b, c = 5) {}).length // 2
|
应用
利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
1 2 3 4 5 6 7 8 9 10
| function throwIfMissing() { throw new Error('Missing parameter'); }
function foo(mustBeProvided = throwIfMissing()) { return mustBeProvided; }
foo() // Error: Missing parameter
|
另外,可以将参数默认值设为undefined,表明这个参数是可以省略的。
function foo(optional = undefined) { ··· }
rest 参数
不需要使用arguments对象了。rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。也可以用到当做函数的参数
1 2 3 4 5 6 7 8 9 10 11
| function add(...values) { let sum = 0;
for (var val of values) { sum += val; }
return sum; }
add(2, 5, 3) // 10
|
注意!! 函数的length属性,不包括 rest 参数。
1 2 3
| (function(a) {}).length // 1 (function(...a) {}).length // 0 (function(a, ...b) {}).length // 1
|
箭头函数
这个是我认为这次ES6最好用的功能之一, 如果你会C#或者Java,你肯定知道lambda表达式,ES6中新增的箭头操作符=>便有异曲同工之妙。它简化了函数的书写。操作符左边为输入的参数,而右边则是进行的操作以及返回的值Inputs=>outputs。最直观体现在函数中的this指向不同,箭头函数的this指向上下文
代码我就不贴出来了,就是简单的 => ,说一下注意点
- 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
- 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
- 不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
比较容易出错的应该就是第一点了,举个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function Timer() { this.s1 = 0; this.s2 = 0; // 箭头函数 setInterval(() => this.s1++, 1000); // 普通函数 setInterval(function () { this.s2++; }, 1000); }
var timer = new Timer();
setTimeout(() => console.log('s1: ', timer.s1), 3100); setTimeout(() => console.log('s2: ', timer.s2), 3100); // s1: 3 // s2: 0
|
尾递归
平常的递归是这样的
1 2 3 4 5 6 7 8
| // 计算n的阶乘,最多需要保存n个调用记录,复杂度 O(n)
function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1); }
factorial(5) // 120
|
尾递归是这样的
1 2 3 4 5 6 7 8
| // 只保留一个调用记录,复杂度 O(1) 。
function factorial(n, total = 1) { if (n === 1) return total; return factorial(n - 1, n * total); }
factorial(5) // 120
|
还有一个比较著名的例子,就是计算 Fibonacci 数列,也能充分说明尾递归优化的重要性。
简单的介绍一下Fibonacci 数列
斐波那契数列 外文名 Fibonacci sequence
别称 黄金分割数列、兔子数列
指的是这样一个数列:1、1、2、3、5、8、13、21、34、……
在数学上,斐波纳契数列以如下被以递归的方法定义:
F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
index:0 1 2 3 4 5 6 7 8 9 10
value:0 1 1 2 3 5 8 13 21 34 55
最简单是这样写的,就是算多的时候,就开始卡主了
1 2 3 4 5
| function Fibonacci (n) { if (n == 1 || n ==2) {return 1};
return Fibonacci(n - 1) + Fibonacci(n - 2); }
|
以前是这样优化的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| var ary = {}; var count = 0; function fibonacci(t) { count++ if(ary[t]) { return ary[t] } else { if(t > 2) { var index = fibonacci(t - 1) + fibonacci(t - 2); ary[t] = index; return index; } else { ary[t] = 1 return 1 } } }
|
用尾递归的方式,就是这样子的
1 2 3 4 5 6
| function Fibonacci2 (n , ac1 = 1 , ac2 = 1) { if(n == 1 || n ==2) {return ac2}; return Fibonacci2 (n - 1, ac2, ac1 + ac2); // 当前所剩次数,当前值,下一个值 // 所以当n为1或者2的时候,输出ac2 }
|
以上就是我对ECMAScript 6函数扩展的一些理解,如果文章由于我学识浅薄,导致您发现有严重谬误的地方,请一定在评论中指出,我会在第一时间修正我的博文,以避免误人子弟。