0%

ECMAScript 6 函数的扩展


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指向上下文

代码我就不贴出来了,就是简单的 => ,说一下注意点

  1. 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
  2. 不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
  3. 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
  4. 不可以使用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
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函数扩展的一些理解,如果文章由于我学识浅薄,导致您发现有严重谬误的地方,请一定在评论中指出,我会在第一时间修正我的博文,以避免误人子弟。

-------------本文结束感谢您的阅读-------------
没办法,总要恰饭的嘛~~