美文网首页
ES6 函数的扩展

ES6 函数的扩展

作者: 貓咪是大王 | 来源:发表于2018-12-17 10:25 被阅读0次

    (一)与结构赋值的默认值结合使用

    //先来看一个例子
    function foo({x,y=1}){
            console.log(x,y)
    }
    
    当函数参数为一个对象,变量才会通过解构赋值生成
    function foo(url,{name="", age=88, other={} }){
            console.log(age);
    }
    
    不能省略第二个参数
    function foo(url,{name='猫咪是大王'}={}){
            console.log(name);
        }
    
    第二个参数可以省略

    对函数的参数设置默认值的两种写法区别

    function methods1({x=0,y=0}={}){//设置对象解构赋值,函数参数的默认值为空对象
            return [x,y];
        }
        function methods2({x,y}={x:0,y:0}){//没有设置对象解构赋值,函数参数的默认值是一个有具体属性的函数
            return [x,y];
        }
    
    函数无参数
    变量都传值
    x传值y不传
    x、y都无值

    (二)参数默认值的位置

    一般定义默认值的参数应该是函数的尾参数,这样比较容易看出省略哪些参数,如果是非尾部的参数设置默认值,则这个参数无法省略

    function foo(x=0,y){
            return [x,y];
        }
    
    function foo(x=1,y=66,z){
            return [x,y,z]
        }
    
    undefined触发该参数等于默认值,而null没有

    (三)函数的length属性

    指定了默认值后,函数的length属性将返回没有指定默认值属性参数个数,即length属性将失真

    var f=function (a){console.log(a)}
        console.log(f.length)
        var ff=function (a=0){console.log(a)}
        console.log(ff.length)
        var fff=function (a,b=0,c=0){console.log(a)}
        console.log(fff.length)
    

    特殊情况:如果设置了默认值的参数不是为参数,那么length属性也不再计入后面的参数

    var f=function (a=1,b,c){console.log(a)}
        console.log(f.length)
    

    (四)作用域

    一旦设置参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域,初始化结束后,这个作用域就会消失,但是这种语法在不设置参数默认值值时是不会出现的

    var x=1;
        function foo(x,y=x){
            console.log(y);
        }
    //调用函数foo时,参数常量形成一个单独的作用域,在这个作用域里面,默认值变量x指向第一个参数x,而不是全局变量x
    
    let x=1;
     function foo(y=x){
            let x=88;
            console.log(y)
        }
    //函数foo调用时,参数y=x形成一个单独的作用域,在这个作用域里面,变量x本身没有定义,
    //所以指向外层的全局变量x,因而函数调用时,函数体内部的局部变量x影响不到默认值变量x
    
    

    如果此时全局变量x不存在则报错

    如果参数默认值为一个函数,该函数的作用域也遵循这个规则

        let foo="out";
        function oFoo(func=x => foo){
            let foo="in";
            console.log(func())
        }
    
        var x=1;
        function foo(x,y = function(){x=2}){
            var x=3;
            y();
            console.log(x)
        }
    //foo的参数形成一个单独的作用域,这个作用域轴线声明了变量x,然后声明了变量y,
    //y的默认值是一个匿名函数,这个匿名函数内部的变量x指向同一个作用域的第一个参数写x,
    //而foo内部又声明了一个内部变量x,该变量与第一个参数x由于不是同一个作用域,所以不是
    //同一个变量。执行y后,内部变量x和外部全局变量x的值都没变
    

    (五)rest参数

    rest参数用于获取函数的多与参数,它搭配的变量是一个数组,该变量将多余的参数放入其中

    function add(...values){
            let sum=0;
            for(var val of values){
                sum +=val;
            }
            return sum;
        }
    

    注意:
    1.函数的length属性不包括rest参数
    2.rest参数只能是最后一个参数,它之后不能再有其他参数,否则会报错

    (六)name属性

    函数的name属性返回该函数的函数名

    function foo(){}
    

    构造函数返回的函数实例的name属性
    bind返回的函数,name属性值会加上bound前缀

    (七)箭头函数

    ES6允许使用 箭头 (=>)来定义函数

    var foo = a => a
    //上面代码等同于
    var foo=function(a){
      return a;
    }
    
    //如果箭头函数不需要参数或者需要多个参数,可以使用圆括号来代表参数部分
    var foo= () => 666
    //等同于
    var foo = function(){
      return 66;
    }
    
    var sum = (a1,a2) => a1+a2;
    //等同于
    var sum=function(a1,a2){
      return a1+a2;
    }
    
    //如果箭头函数的代码部分多于一条语句,则要使用大括号将其括起来,并使用return语句返回
    var sum = (a1,a2) => { return a1+a2;}
    
    //由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号
    var getArticles = id => ({ id:id, name:"猫咪是大王"});
    
    //箭头函数也可以与变量的解构赋值结合使用
    const full = ({ first, last}) => first + ' ' +last;
    //等同于
    function full(person) {
      return person.first+ ' '+person.last;
    }
    

    箭头函数的一个用处就是简化回调函数

    [1,2,3].map(x => x * x);
    //等同于
    [1,2,3].map(function(){
      return x * x;
    })
    
    var result = values.sort((a,b) => a-b)
    //等同于
    var result=values.sort(function(a,b){
      return a - b;
    })
    

    rest 参数与箭头函数结合

    const num=(...nums) => nums
    
    const headAndfoot=(head,...foot) =>[head,foot]
    

    注意:
    1.函数体内的this对象就是定义时所在的对象,而不是使用时所在对象
    this对象的指向是可变的,但是在箭头函数中是固定的

        function foo(){
            setTimeout(() => {
                console.log('id', this.id);
            },1000)
        }
        var id=66;
    //如果是普通函数,执行this应该指向全局对象window,输出66,但是箭头函数导致this总是指向函数定义生效时所在的对象,所以输出88
    
    //箭头函数可以让setTimeout里面的this绑定定义时所在的作用域而不是指向运行时所在的作用域
        function Timer(){
            this.a1=0;
            this.a2=0;
            // 箭头函数
            setInterval(() => this.a1++, 1000);
            // 普通函数
            setInterval(function(){
                this.a2++;
            },1000);
        }
        var timer = new Timer();
        setInterval(() => console.log('a1:',timer.a1),3100);
        setInterval(() => console.log('a2:',timer.a2),3100);
    

    2.箭头函数不可以当构造函数,不能使用new命令,否则报错

    var foo=() => 88;
    

    3.不可以用arguments对象,该对象在函数体不存在,如果要用,可以用rest参数代替

    var foo=() => {
            console.log(arguments);
            return 1;
    }
    

    4.不可以用yield命令,因此箭头函数不能用作Generator函数

    一个Generator函数与普通function的区别就是函数名前面多了一个星号 * 但是执行时有很大不同,
    与yield命令配合,可以实现暂停执行的功能
    

    以下代码中有几个this

        function foo(){
            return () =>{
                return () =>{
                    return () =>{
                        console.log('id',this.id);
                    };
                };
            };
        }
        var f =foo.call({id:1});
    
        var f1=f.call({id:2})()();
        var f2=f().call({id:3})();
        var f3=f()().call({id:4});
    上面的代码只有一个this,就是foo上午this,所以f1,f2,f3都输出同样的结果。因为所有的内层函
    数都是箭头函数,都没有自己的this,他们的this都是最外层foo函数的this
    

    除了this外,arguments,super,new.target在箭头函数中也是不存在的
    箭头函数没有自己的this,当然也不能用call()、apply()、bind()这些方法去改变this的指向

    嵌套的箭头函数

    //es5语法
        function insert(value){
            return {into:function (array){
                return {after:function(afterValue){
                    array.splice(array.indexOf(afterValue) +1,0,value);
                }};
            }};
        }
        insert(2).into([1,3]).after(1);
    
    //es6语法
        let insert=(value) => ({into:(array) => ({after:(afterValue)=>{
            array.splice(array.indexOf(afterValue) + 1, 0, value);
            return array;
        }})})
    

    (八)绑定this

    箭头函数可以绑定this对象,大大减少了显式this对象的写法(call、apply、bind),但是并非适用于所有场合
    函数绑定运算符是并排的双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象作为上下文环境(即this对象)绑定到右边的函数上。

    foo::bar;
    //等同于
    bar.bind(foo);
    
    foo::bar(...arguments);
    //等同于
    bar.apply(foo,arguments);
    

    如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上

    var method=obj::obj.foo;
    //等同于
    var method = ::obj.foo;
    
    let log=::console.log;
    //等同于
    var log=console.log.bind(console)
    

    由于双冒号运算符返回的还是原对象,因此可以采用链式写法

        import {map, takeWhile,forEach} from "iterlib";
        getPlayers()
        ::map(x=> x.character())
        ::takeWhile(x=> x.strength>100)
        ::forEach(x => console.log(x))
        
        let {find,html}=jake;
        docment.querySelectorAll("div.myClass")
        ::find("p")
        ::html("lalala")
    
    

    (九)尾调用优化

    尾调用是函数式编程的一个重要概念,是指某个函数的最后一步调用另一个函数

    function f(x){
      return g(x)
    }
    

    一些错误的实例

    //1.
    function f(x){
      let y=g(x);
      return y;
    }  //调用函数g之后还有操作
    
    //2.
    function f(x){
            return g(x)+1;
        } //调用函数g之后还有操作
    
    //3.
    function f(x){
            g(x);
        }
    //等同于
    function f(x){
            g(x);
            return undefined;
        }
    

    尾调用不一定出现在尾部,只要是最后一步操作即可

    function f(x){
            if(x>0){
                return m(x)
            }
            return n(x);
        }//函数m和n都属于尾调用,因为他们都是函数f的最后一步操作
    

    尾调用优化

    • 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,直接用内层函数的调用帧,取代外层函数的调用帧就可以了。
        function f(){
            let m=1;
            let n=2;
            return g(m+n);
        }
        f();
    
        //等同于
        function f(){
            return g(3);
        }
        f();
    
        //等同于
        g(3)
    

    尾递归
    函数自己调用自己称为递归,尾调用自身就称为尾递归
    递归非常耗内存,我在菜鸟题中有提过,因为需要同时保存成百上千个调用帧,所以很容易发生栈溢出,但是对于尾递归来说,只存在一个调用帧,所以永远不会发生栈溢出错误

        function f(n,f1=1,f2=1){
            if(n<=1){
                return f2;
            }
            return f(n-1,f2,f1+f2)
        }
    

    (十)严格模式

    ES6的尾调用只在严格模式下开启,正常模式下无效,因为在正常模式下函数内部会有两个变量,可以跟踪函数的调用栈

    • func.arguments:返回调用时函数的参数
    • fun.caller:返回调用当前函数的那个函数
      尾调用优化发生时,函数的调用栈会改写,因此上面两个变量就会失真。严格模式禁用这两个变量,所以尾调用仅在严格模式下生效

    相关文章

      网友评论

          本文标题:ES6 函数的扩展

          本文链接:https://www.haomeiwen.com/subject/jlbacqtx.html