• ES6-04:函数的扩展


    ES6 针对新的语法特性(解构、参数默认值、箭头语句、块级作用域let),对于函数的属性、参数、作用域进行了扩展,并对递归调导致内存栈溢出用进行了优化。
    同时ES6规定,只要函数参数使用了默认值、解构赋值、扩展运算符,函数内部都不可以使用严格模式(ES5可以),否则会报错;

    1. 函数参数默认值

    ES6之前,函数定义/声名时不能指定默认值;ES6可以在函数定义时进行初始化;同时有以下几点需要注意:

    • 参数设置默认值时,参数直接写在默认值后面,如:function(x,y=1){return x+y};
    • 函数传递参数时,不允许有同名参数,如:function(x,x,y=true){return x};//报错:参数重复命名
    • 函数传递参数时,不能在函数体内部用letconst再次声明,如:function(x){let x=false;}//报错;
    • rest参数,用于获取多余的参数,可替代arguments,rest参数之后不可再有其他参数,否则会报错:
    function sum(...values){
     let sum=0;
     for(let val of values){
         sum+=val;
        }
        return sum;
    }
    sum(1,2,3,4);//10
    
    • 传递默认参数时,默认参数需放在无默认值参数后面,如果不在最后,需要显示传入undefiend
    function fn(x,y=5,z){
        return [x,y,z];
    }
    fn();   // [undefined,5,undeifined]
    fn(1);  //[1,5,undefiend]
    fn(1,2);// [1,2,undefined]
    fn(1,,3);//报错
    fn(1,undefined,3);//[1,5,3]
    
    • 传参与结构赋值结合:
    // 写法一
    function fn1({x=0,y=0}={}){
        return [x,y];
    }
    //写法二
    function fn2({x,y}={x=0,y=0}){
        return [x,y];
    }
    // ① 函数无参数
    fn1();  // [0,0]
    fn2(); // [0,0]
    // ② x、y都无值情况;
    fn1({});   // [0,0]
    fn2({});  // [undefined,undefined]
    // ③x有值,y无值;
    fn1({x:3}});  // [3,0]
    fn2({x:3});  // [3,undefiend]
    

    2.函数作用域

    函数参数设置默认值后,函数初始化时,参数会形成一个单独的作用域;等到初始化完成后,这个作用域消失;不设置默认值时,该机制不生效;

    // ① 函数参数独立作用域机制赋值:
    let x=1;
    function fn1(x,y=x){console.log(y)};
    fn1(2);//2
    //② 函数参数独立作用域+作用域链
    let a=1;
    function fn2(b=a){
    let a=2;
    console.log(b);
    };
    fn2();//1
    
    // ③ 函数参数单独作用域+作用域链
    function fn3(e=d){
    console.log(e)
    }
    fn3();// ReferenceError:e is not defiined
    
    // ④ 参数赋初值+let 暂时性死区 =>报错
    let j=2;
    function fn4(j=j){
    console.log(j)
    }
    fn4();//ReferenceError:j is not defined  
    

    3. 函数新增属性

    • name:返回函数名称:function fn(){};fn.name;//fn

    ①将匿名函数赋值给变量,ES5中该属性返回空字符串"",而ES6返回实际函数名;②Function构造函数返回函数实例,name属性返回anoymous,bind返回的函数,name属性加上bound前缀:

    (new Function).name;         //"anoymous"
    function fn(){};
    fn.bind({}).name;                // "bound fn"
    (function(){}).bind({}).name; // "bound"
    
    • length:返回函数没有指定默认值的参数的个数,本质是返回该函数预期传入的参数个数,即指定默认值的参数,length属性将失效(忽略该参数)、同理rest参数页不会计入length属性;
    (function(a){}).length;//1
    (function(a=1){}).length;//0
    (fucntion(a,b,c=5){}).length;//2
    
    (function(..args){}).length;//0
    (function(a,b=1,c,d){}).length;//1
    

    4. 箭头函数——()=>{}

    箭头函数不存在单独的作用域,即不存在单独的this、arguments、super、new.target;这四个对象分别指向外层函数对应的变量;
    箭头函数中this指向固化并不是箭头函数内部有绑定this的机制,相反,本质原因是箭头函数根本没有自己的this,导致箭头函数内部的this就是外层代码块的this,也正是因为没有this,即没有独自的作用域,所以不能用作构造函数;

    1.注意事项
    • 不存在this:箭头函数体内的this对象指向定义时所在对象,而非调用时所在对象;
    • 不可做构造函数:箭头函数不可用实例化,即不可用new创建,否则报错;
    • 不可使用arguments对象:arguments对象在箭头函数体内部存在,可用rest参数替代;
    • 不可使用yeild命令:即箭头函数不能用作Generator函数;
    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);  // s1:3
    setTimeout(()=>console.log('s2:',timer.s2),3100); // s2:0
    
    2. 箭头函数使用场景
    • 简化回调函数;
    • 绑定this:自动绑定this到外层函数对象,减少显示绑定this对象写法(call、apply);
    • 部署管道机制(pipeline):即前一个函数的输出时后一个函数的输入;
    const plus=a=>a+1;
    const mult=b=>b*2;
    mult(plus(5));//12
    
    const pipeline=((...fns)=>val=>fns.reduce((a,b)=>b(a),val));
    const addThenMult=pipeline(plus,mult);
    addThenMult(5);//12
    

    5.函数优化

    四个概念:调用帧、尾调用函数、尾递归函数、蹦床函数、函数柯里化(currying)

    • 调用帧:

      函数调用时会在内存中形成一个调用记录(调用帧-call frame),保存调用位置和内部变量等息息;如果A函数的内部调用B函数,那么在A的调用帧上方会形成一个B函数的调用帧,待B函数运行结束,将结果返回A时,B函数的调用帧占用的内存才会被回收消失;如果B函数内部还调用了C函数,就会生成一个C函数的调用帧,以此类推,所有的调用帧形成一个调用栈(call stack);
      递归函数非常消耗内存,因为需要同时保存数量巨大的调用帧,容易造成“栈溢出”错误(stack overflow);

    • 尾调用函数: 尾调用函数(Tail call):即某个函数的最后一步是返回调用另一个函数,如:function f(x){return g(x)};尾调用不一定出现在函数的尾部,但一定是最后一步操作,以下三种均不属于尾调用:

    function fn1(){return g()+1;}              // 调用后还有其他操作
    function fn2(){let res=g();return res;} // 不是最后一步操作
    function fn3(){g()}                          // 返回 undeifend
    
    • 尾递归函数: 尾调用函数的调用函数为函数自身时,成为尾递归函数;
    // ① ES6尾递归优化案例:阶乘
    function fn1(n){
        if(n===1) retrun 1;
        return n*fn(n-1);
    }
     fn1(5; // 120
    funcion fn1Optimize(n,tatal){
        if(n===1)return 1;
        return fn1Optimize(n-1,n*total);
    }
    fn1Optimize(5,1); //120
    // ② ES6尾递归优化案例: 斐波那数列
    function fibonacci(n){
        if(n<=1) return 1;
        return fibonacci(n-1)+fibonacci(n-2);
    }
    fibonacci(10); //89
    fibonacci(100); //堆栈溢出
    fibonacci(1000); // 堆栈溢出
    
    function fibonacciOpt(n,a1=1,a2=1){
         if(n<=1) return a2;
         return fibonacciOpt(n-1,a2,a1+a2)
    }
    fibonacciOpt(10); //89
    fibonacciOpt(100); // 573147844013817200000
    fibonacciOpt(1000); // 49 7.0330367711422765e+208
    fibonacciOpt(10000);//  Infinity 或 Uncaught RangeError: Maximum call stack size exceeded
    
    • 蹦床函数(trampoline):可以将尾递归转换为循环执行;进而避免递归执行,消除调用栈溢出,蹦床函数的实现方法实例;
    function trampoline(fn){
        while(f&&f instanceof Function){fn=fn()};
        return fn;
    }
    
    • 函数柯里化:函数式编程中,函数柯里化指 将多参数的函数转换成单个参数形式的函数;
    function currying(fn,n){
        return function(m){
            return fn.call(this,m,n)
        }
    }
    
    • 优化方案: ① 外层函数封装多参数的尾递归函数,如:function fn(n){function g(n,1){return g(n,1) }};②函数柯里化

      只有当调用函数不再需要外层函数的内部变量,内层函数的调用帧才会取代外层函数的调用帧,否则无法进行尾调用优化;
      优化方案:尾调用、尾递归由于是函数的最后一步,调用位置、内部变量信息等都不会再用到了,所以不需要保留外层函数的调用帧,直接用内层函数的调用帧取代外层函数即可,这就是尾调用优化(Tail call Optimize)、尾递归函数优化;

      由于尾调用优化的重要性,ES6第一次明确规定: 所有ECMAScript的实现都必须部署“尾调用优化”;即ES6中只要使用尾递归,就不会发生内存溢出;

    ps:

    • ES7 提案函数对象绑定运算符(babel已支持)::: 双冒号左边是一个对象,右边是一个函数,该运算符自动将左边对象作为右边函数的上下文环境(context);
  • 相关阅读:
    RESTFul-service guideline
    logback推荐配置
    MongoDB 存活時間 TTL 用法
    IntelliJ IDEA 的热部署插件JRebel 安装及使用(破解)
    Idea远程调试
    学习方法记录
    2017年3月9日日记
    maven整合SSM总结
    【转】 SSM框架——详细整合教程(Spring+SpringMVC+MyBatis)
    【转】如何用Maven创建web项目(具体步骤)
  • 原文地址:https://www.cnblogs.com/hbzyin/p/8012299.html
Copyright © 2020-2023  润新知