关于ES6中的函数的扩展,我会分六点
一,结合解构赋值,函数参数的改变及默认值分配
1,普通默认初始化
function test (x = 'x', y = 'y') {
console.log('x', x) // x
console.log('y', y) // y
}
test()
2、结合解构赋值,参数初始化
function test ({x = 'x', y = 'y'}) {
console.log('x', x) // x
console.log('y', y) // y
}
test({})
3、参数初始化值为变量,遵循就近原则
const x = '333'
const Z = 'z'
function test ({x = 'x', y = x, z = Z}) {
console.log('x', x) // x
console.log('y', y) // y
console.log('z', z) // z
}
test({})
4、结合解构,多参数打包捆绑
function test ({ ...rest }) {
const { x, y } = rest
console.log('x', x) // x
console.log('y', y) // y
}
test({ x: 'x', y: 'y' })
二、箭头函数特点及注意事项
1、特点一
函数没有this
,因此内部若使用this
,指向的为其父类的作用域。
2、特点二
const test = (x = 'x') => x
// 等同于
const test = (x = 'x') => {
return x
}
3、注意点一
不可当作构造函数
const test = (x = 'x') => {
console.log(x)
return x
}
const lala = new test()
// topAtrribute.html:17 Uncaught TypeError: test is not a constructor
4、注意点二
我经过尝试,可以作用域yield,可却书上说不行,待进一步确认
const test = (x = 'x') => {
console.log(x)
return x
}
const fet = function* fetch() {
yield test()
return 'end'
}
fet().next() // x
三、尾调用【思想:函数式编程(重要,以后要注意函数链用的位置了,以前没有意识到这一点);定义:在某个函数的最后一步调用另外一个函数】
如:
const test = () => {
codeing...
return g(x) // 在最后一步return抛出的时候调用函数g(x)
}
以下三种情况都不是尾调用
// 情况一
// 在g(x) + 1这步调用函数,而不是最后抛出return (g(x) + 1)这步调用
const test = () => {
codeing...
return g(x) + 1
}
// 情况二
// 在函数体内部调用函数,而不是在最后一步抛出return undefined这步调用
const test = () => {
g(x)
}
// 情况三
// 同情况二类似,是在函数体的赋值语句调用,而不是抛出return y【g(x)函数的return 值】调用
const test = y => {
y = g(x)
return y
}
四、思考的同学肯定会想,为什么推荐使用尾调用,有什么好处呢?【关于尾调用的优化只是在ES6模式内】
1、首先我们得有个前提知识,就是了解函数每次的调用都会在内存里面产生一个“调用帧”,用于保存调用环境【位置】和内部的变量信息,直到函数运行完毕才消除调用帧。
2、我们再想想如果我们对函数的调用,不利用尾调用优化,则会产生一种嵌套效应【大娃娃里面套中娃娃里面再套小娃娃】,这样你感觉你只是调用了一个函数,但是在内存中同时保存了其【你显式调用的函数】内部调用的N多函数的调用帧,甚至以致于内存溢出。
3、还记得尾调用的定义吗?在函数最后一步调用其它函数。对的,没错,是在最后一步,那样子就相当于剥洋葱一样,直到最外层的函数调用完毕那刻才调用另外一个函数,这样即使是链式调用,在内存中也只有一个当前调用函数的调用帧。
五、尾递归
正常来说,递归函数对内存造成了巨大的挑战,因为同时要存储成百上千的调用帧,那么如果利用尾调用的原理使普通递归变为尾递归岂不是无论递归多深,只有一个调用帧,也就永远不会发生内存溢出的问题
那怎么将普通的递归函数变为尾递归函数呢?【在此借用阮老师ES6相关的例子讲解】
1、阶乘递归
function factorial(n) {
if (n === 1) return 1;
return n * factorial(n - 1);
}
factorial(5) // 120
function factorial(n, total) {
if (n === 1) return total;
return factorial(n - 1, n * total);
}
factorial(5, 1) // 120
我们仔细观察两者有什么不同。
第一个函数,其实存在一个隐式的临时变量 (n*factorial(n - 1))
,只是利用查询代替临时变量的手法隐藏了而已。
第二个函数,确实没有临时变量的存在,但是这是利用函数的参数取代临时变量,这样子的好处就是可以将普通的递归函数构造成可以利用尾调用手法调用的递归函数,从而节省了内存大大批量同时占用。
2、再看看计算 Fibonacci 数列
function Fibonacci (n) {
if ( n <= 1 ) {return 1};
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
Fibonacci(10) // 89
Fibonacci(100) // 超时
Fibonacci(500) // 超时
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.prototype.toString()和try…catch…的微改
1、Function.prototype.toString()
明确要求返回一模一样的原始代码。
2、try....catch....
允许catch语句省略参数(catch(err
){ codeing… })。