ES6 函数的扩展
箭头函数
- 箭头函数直接返回一个对象,必须在对象外面加上括号,否则会报错
- 如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用
return
语句返回 - 简化回调函数
- 箭头函数体内的
this
对象,就是定义时所在的对象,而不是使用时所在的对象,在箭头函数中,它是固定的。
而普通函数 this
指向运行时所在的作用域(即全局对象)
- 不可以当作构造函数,也就是说,不可以使用
new
命令 ,否则会抛出一个错误。this 指向的固定化,并不是因为箭头函数内部有绑定 this 的机制,实际原因是箭头函数根本没有自己的 this,导致内部的 this 就是外层代码块的 this。正是因为它没有 this,所以也就不能用作构造函数。也就不能用call()
、apply()
、bind()
这些方法去改变 this 的指向
// 箭头函数没有自己的 this,所以 bind 方法无效,内部的 this 指向外部的 this
(function() {
return [
(() => this.x).bind({ x: 'inner' })()
];
}).call({ x: 'outer' });
// ['outer']
- 不可以使用
arguments
对象,该对象在函数体内不存在 。如果要用,可以用 rest 参数代替。
arguments
、 super
、 new.target
三个变量在箭头函数之中也是不存在的
- 不可以使用
yield
命令,因此箭头函数不能用作Generator
函数
不适用场合
定义对象的方法,且该方法内部包括 this
因为对象不构成单独的作用域 ,导致 jumps 箭头函数定义时的作用域就是全局作用域。调用 cat.jumps()
时,如果是普通函数,该方法内部的 this 指向 cat;如果写成上面那样的箭头。
函数,使得 this 指向全局对象,因此不会得到预期结果。
const cat = {
lives: 9,
jumps: () => {
this.lives--;
}
}
需要动态 this 的时候,也不应使用箭头函数
下面代码运行时,点击按钮会报错,因为 button 的监听函数是一个箭头函数,导致里面的 this 就是全局对象。如果改成普通函数,this 就会动态指向被点击的按钮对象。
var button = document.getElementById('press');
button.addEventListener('click', () => {
this.classList.toggle('on');
});
如果函数体很复杂,有许多行,或者函数内部有大量的读写操作,不单纯是为了计算值,这时也不应该使用箭头函数,而是要使用普通函数,这样可以提高代码可读性
尾调用优化/尾递归优化
注意: ES6 的尾调用优化只在严格模式下开启 ,正常模式是无效的。因为在正常模式下,函数内部有两个变量,可以跟踪函数的调用栈
func.arguments
:返回调用时函数的参数。func.caller
:返回调用当前函数的那个函数
函数调用会在内存形成一个 调用记录,又称 调用帧(call frame),保存调用位置和内部变量等信息。所有的调用帧,就形成一个 调用栈(call stack)。
只有不再用到外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则就无法进行“尾调用优化”,确保最后一步只调用自身,把所有用到的内部变量改写成函数的参数 。
// 尾递归
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5, 1) // 120
// 非尾递归 Fibonacci 数列
function Fibonacci (n) {
if ( n <= 1 ) {return 1};
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
//尾递归优化
function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
if( n <= 1 ) {return ac2};
return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}
Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity
尾递归优化的实现
采用 循环 换掉 递归
function sum(x, y) {
if (y > 0) {
return sum(x + 1, y - 1);
} else {
return x;
}
}
sum(1, 100000)
// Uncaught RangeError: Maximum call stack size exceeded(…)
// 蹦床函数(trampoline)可以将递归执行转为循环执行
function trampoline(f) {
while (f && f instanceof Function) {
f = f();
}
return f;
}
function sum(x, y) {
if (y > 0) {
return sum.bind(null, x + 1, y - 1); // 每一步返回另一个函数
} else {
return x;
}
}
// 使用蹦床函数执行 sum,就不会发生调用栈溢出。
trampoline(sum(1, 100000)) // 100001
蹦床函数并不是真正的尾递归优化,下面的实现才是
function tco(f) {
var value;
var active = false;
var accumulated = [];
return function accumulator() {
accumulated.push(arguments);
if (!active) {
active = true;
while (accumulated.length) {
value = f.apply(this, accumulated.shift());
}
active = false;
return value;
}
};
}
var sum = tco(function(x, y) {
if (y > 0) {
return sum(x + 1, y - 1)
}
else {
return x
}
});
sum(1, 100000)
// 100001
tco 函数是尾递归优化的实现,它的奥妙就在于状态变量 active。默认情况下,这个变量是不激活的。一旦进入尾递归优化的过程,这个变量就激活了。然后,每一轮递归 sum 返回的都是 undefined
,所以就避免了递归执行;而 accumulated 数组存放每一轮 sum 执行的参数,总是有值的,这就保证了 accumulator 函数内部的 while 循环总是会执行。这样就很巧妙地将“递归”改成了“循环”,而后一轮的参数会取代前一轮的参数,保证了调用栈只有一层
柯里化
将多参数的函数转换成单参数的形式
// 第一种
function currying(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 = currying(tailFactorial, 1);
factorial(5) // 120
// 第二种
function factorial(n, total = 1) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5) // 120
函数参数的尾逗号
catch 命令的参数省略
try {
// ...
} catch {
// ...
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论