函数绑定运算符
- 使用方式:用并排的两个冒号来表示
::
,双冒号左边是一个对象,右边是一个函数.该运算符会自动将左边的对象作为上下文环境(this). - 例如:
foo::bar
等价于bar.bind(foo)
- 如果双冒号左边为空,右边是一个对象的话,则等于将该方法绑定在该对象上.例如:
var method=obj::obj.foo
等价于var method=::obj.foo
尾调用优化
- 所谓尾调用就是指某个函数的最后一步是调用另一个函数
//尾调用的基本形式
function f(x){
return g(x); //这里如果是return g(x)+1;则不属于尾调用.也就是说尾调用之后不能有任何操作
}
- 尾调用优化的原理:函数在调用的时候会在内存形成一个调用记录,又称调用帧,保存调用位置和内部变量等信息。如果在函数A内调用函数B,那么在A的调用帧上方还会有一个函数B的调用帧,等到函数B执行完成返回结果给A,函数B的调用帧才会消失。尾调用由于是函数的最后一步,所以不需要保留外层函数的调用帧,因为调用位置、内部变量不会再用到了,只需要直接用内部函数的调用帧取代外层函数的调用帧即可。这就叫尾调用优化,即只保留内层函数的调用帧
function f(){
let m=1;
let n=2;
return g(m+n);
}
f();
//等价于
function f(){
return g(3);
}
//等价于
g(3);
- 如果尾调用还要用到外层函数的内部变量,则不能进行尾调用优化
function addOne(a){
var one=1;
function inner(b){
return b+one; //用到了外层函数addOne的n内部变量one,因此不能优化
}
return inner(a);
}
- ES6的尾调用优化只在严格模式下开启,正常模式无效,因为正常模式下函数内部的arguments和caller(返回调用当前函数的函数)可以跟踪函数的调用栈
尾递归
- 尾递归即尾调用自身
- 尾递归的优势:递归非常消耗内存,因为需要同时保护成百上千个调用帧,很容易发生栈溢出,但是尾递归只存在一个调用帧,所以不会发生栈溢出
- 举例:
//常规递归阶乘函数
function f(n){
if(n===1) return 1;
return n*f(n-1);
}
//尾递归改写
function f(n,total=1){
if(n===1) return total;
return f(n-1,n*total);
}
f(5) ==>120
//常规斐波那契数列
function f(n){
if(n<=2) return 1;
return f(n-1)+f(n-2);
}
//尾递归改写
function f(n,a=1,b=1){
if(n<=2) return b;
return f(n-1,b,a+b);
}