作用域
- 一旦设置了默认参数,函数进行声明初始化时,参数会形成一个单独的作用域(context)。 等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的。 见例子:
例子1: let x = 1;function func (x, y = x) {console.log(y) // 输出2}func(2)例子二:let x = 1;function f(y = x) { let x = 2; console.log(y); // 输出1}f()例子三:报错function f(y = x) { let x = 2; console.log(y);}f() // ReferenceError: x is not defined
rest参数
- rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中,这样就不需要使用arguments对象了; 见例子:
function add (...value) {let sum = 0;for (let i of value) {sum += i;}return sum;}add(3, 5, 7); // 15
箭头函数
- () => {}; // 匿名函数
- n => n + 1;
- n => ({id: n}); 返回对象要用括号括起来,因为{}被解释为代码块;
注意点
- 箭头函数没有this,准确来说,箭头函数的this是其函数外的this
- 不可以当构造函数
- 不可使用argument,可以使用rest代替
- 不可使用yield命令,箭头函数不能作为Generator
- arguments、super、new.target和this一样指向外层函数的对应变量
双冒号运算符
- ::双冒号左边是一个对象,右边是一个函数
- 该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面
尾调用
见下面几个例子:
function factorial(n) { if (n === 1) return 1; return n * factorial(n - 1);}factorial(5)
复杂度为o(n) 太大会造成堆栈溢出
function factorial(n, total) { if (n === 1) return total; return factorial(n - 1, n * total);}factorial(5, 1) //
复杂度为哦o(1)
尾递归函数的改写:
-
尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数
-
递归本质上是一种循环操作。纯粹的函数式编程语言没有循环操作命令,所有的循环都用递归实现,这就是为什么尾递归对这些语言极其重要。 对于其他支持“尾调用优化”的语言(比如 Lua,ES6),只需要知道循环可以用递归代替,而一旦使用递归,就最好使用尾递归。
-
解决方案: 1、蹦床函数的一个实现,它接受一个函数f作为参数。只要f执行后返回一个函数,就继续执行。 注意,这里是返回一个函数,然后执行该函数,而不是函数里面调用函数,这样就避免了递归执行,从而就消除了调用栈过大的问题。
例子:
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; }}trampoline(sum(1, 100000))// 100001
2、真的优化方案
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)
- 上面代码中,tco函数是尾递归优化的实现,它的奥妙就在于状态变量active。默认情况下, 这个变量是不激活的。一旦进入尾递归优化的过程,这个变量就激活了。然后,每一轮递归sum返回的都是undefined,所以就避免了递归执行;而accumulated数组存放每一轮sum执行的参数,总是有值的,这就保证了accumulator函数内部的while循环总是会执行。这样就很巧妙地将“递归”改成了“循环”,而后一轮的参数会取代前一轮的参数,保证了调用栈只有一层。