ES6学习-函数

作者: 厂厂哥 | 来源:发表于2017-09-20 22:40 被阅读26次
    函数形参的默认值

    JavaScript函数有一个特别的地方,无论在函数定义中声明了多少形参,都可以传入任意数量的参数,也可以在定义函数时添加针对函数数量的处理逻辑,当已定义的形参无对应参数时传入指定的默认值。

    在ES5中模拟默认参数
    function makeRequest(url,timeout,callback){
         timeout=timeout||2000;
         callback=callback||function(){//其它内容};
    }
    //当timeout=0时,会出现异常。
    //这种情况下使用typeof检查参数类型更安全
    function makeRequest(url,timeout,callback){
         timeout=(typeof timeout!==undefined)?timeout:2000;
         callback=(typeof callback!==undefined)?callback:function(){//其它内容};
    }
    //这种方式虽然安全但是需要额外的代码执行操作。
    
    在ES6中的默认参数值

    ES6简化了形参提供默认值的情况,如果没有参数传入值则为其提供一个。

    function makeRequest(url,timeout=2000,callback=function(){}){
          //函数的其它部分。
    }
    //使用
    makeRequest("/foo");//使用timeout和callback的默认值
    makeRequest("/foo,500");//使用callback的默认值
    makeRequest("/foo,500,function(body){doSomething(body);}");//不使用默认值
    //声明函数时,可以为任意参数指定默认值,在已指定默认值的参数后可以继续声明无默认值参数。
    function makeRequest(url,timeout=2000,callback){
         //其它内容
    }
    //调用
    makeRequest("/foo",undefined,function(body){return body;});//使用timeout默认值。
    makeRequest("/foo");//使用timeout默认值。
    makeRequest("/foo",null,function(body){return body;});//使用timeout默认值。
    //null是一个合法值。
    
    默认参数值对arguments对象的影响

    切记,当使用默认参数值时,arguuments对象行为和以往不同。

    function mixArgs(first,second){
        console.log(first===arguments[0]);//true
        console.log(second===arguments[1]);//true
        first="c";
        second="d";
        console.log(first===arguments[0]);//true
        console.log(second===arguments[1]);//true
    }
    

    在非严格模式下,命名参数的变化会同步跟新到arguments对象中。
    在严格模式下,取消了这个行为,arguments对象不再改变。

    function mixArgs(first,second){
            "use strict"
        console.log(first===arguments[0]);//true
        console.log(second===arguments[1]);//true
        first="c";
        second="d";
        console.log(first===arguments[0]);//false
        console.log(second===arguments[1]);//false
    }
    

    ES6中,如果一个函数使用了默认参数值,则无论是否显式定义了严格模式,arguments对象都与ES5严格模式一致。

    function mixArgs(first,second="b"){
        console.log(arguments.length);//1
        console.log(first===arguments[0]);//true
        console.log(second===arguments[1]);//false
        first="c";
        second="d";
        console.log(first===arguments[0]);//false
        console.log(second===arguments[1]);//false
    }
    mixArgs("a");
    
    默认函数表达式

    关于默认参数值,最有趣的特性可能就是非原始值传参。

    function getValue(){
        return 5;
    }
    function add(first,second=getValue()){
        return first+second;
    }
    console.log(add(1,1,));//2
    console.log(add(1));//6
    //在这段代码中,如果不传入最后一个参数,就会调用getValue()方法,注意:函数声明时不会调用。
    let value=5;
    function getValue(){
        return value++;
    }
    function add(first,second=getValue()){
        return first+second;
    }
    console.log(add(1,1));//2
    console.log(add(1));//6
    console.log(add(1));//7
    

    注意,当使用函数调用结果作为默认参数值时,如果忘记写小括号,传入的是对函数的调用。

    //可以用先定义的参数作为后定义参数的默认值,反过来不行。
    function add(first,second=first){
        return first+second;
    }
    console.log(add(1,2));//3
    console.log(add(1));//2
    
    默认函数的临时死区
    function add(first=second,second){
        return first+second;
    }
    console.log(add(undefined,1));//报错
    //调用时相当于:
    let first=second;
    let second=1;
    
    ES5中的无命名函数
    function pick(object){
        let result=Object.create(null);
        //从第二个参数开始
        for(let i=1,len<arguments.length;i<len;i++){
            result[arguments[i]]=object[arguments[i]]
        }
        return result;
    }
    let book={
        title:"Understanding ECMAScript",
        author:"Nicholas C.Zakas",
        year:2016
    }
    let bookData=pick(book,"author","year");
    console.log(bookData.author);//"Nicholas C.Zakas"
    console.log(bookData.year);//2016
    

    用不定参数改写就可以解决索引不是1的问题。

    不定参数

    在函数的命名参数前添加三个点(...)就表明这是一个不定参数。

    //改写pick()函数。
    function pick(object,...keys){
        let result=Object.create(null);
        for(let i=0,len=keys.length;i<len;i++){
            result[keys[i]=object[keys[i]];
        }
        return result;
    }
    //好处是只要一眼就可以看出参数使用数量。
    //注意:arguments包含所有传入参数,而length统计的是函数命名参数的数量。
    

    不定参数使用有一定的限制。
    ①每个函数只能有一个不定参数,而且只能放在最后。
    ②不定参数不能使用对象字面量setter之中。

    增强的Function函数

    ES6增强了Function构造函数的功能,支持在创建函数时,定义默认参数和不定参数。

    var add=new Function("first","second=first","return first+second");
    console.log(add(1,2));//3
    console.log(add(1));//2
    //使用不定参数
    var pickFirst=new Function("...args","return args[0]");
    console.log(pickFirst(1,2));//1
    
    展开运算符

    在所有的新功能中,展开运算符和不定参数最相似。不定参数可以让你指定多个独立的参数,并通过整合后的数组来访问;而展开运算符可以让你指定一个数组,将它们打散后作为自己独立的参数传入函数。在大多数使用apply的方法使用展开运算符有一定好处。

    let value1=25,value2=50;
    console.log(Math.max(value1,value2));//50
    //Math.max()方法可以接受任意数量的参数并返回值最大的那个。
    let values=[25,50,75,100]
    console.log(Math.max.apply(Math, values));//从数组中选择
    //使用ES6可以简化
    //等价于
    console.log(Math.max(...values));//100
    //可以传入单独限定值
    console.log(Math.max(...values,90));//100
    
    name属性

    ES6中会打印名字属性。
    注意,通过get,set,bind创建的函数名字有前缀。

    元属性new.target

    为了判断函数是否通过new调用,ES6引入了new.target这个元属性(指非对象属性,可以提供非对象目标的补充信息)。

    function Person(name){
        if(typeof new.target!=="undefined"){
            this.name=name;
        }else{
            throw new Error("你必须通过new关键字来调用")
        }
    }
    var person=new Person("changchang");
    var notPerson=Person.call(person,"changchangge");//抛出错误
    //在函数外使用new.target是个语法错误。
    
    块级元素

    在ES6中,块级声明不会抛出错误,,作用域就是代码块。

    //严格模式下
    "use strict"//严格模式下提升到代码块顶部。
    if(true){
        console.log(typeof doSomething);//"function"
        function doSomething(){
            //空函数
        }
        doSomething();
    }
    console.log(typeof doSomething);//"undefined"
    //非严格模式,函数不提升至代码块顶部,而是提升到外围函数或者全局作用域。
    if(true){
        console.log(typeof doSomething);//"function"
        function doSomething(){
            //空函数
        }
        doSomething();
    }
    console.log(typeof doSomething);//"function"
    
    箭头函数

    箭头函数与传统函数有些不同,主要集中在以下方面:
    ·没有this、super、arguments和new.target绑定。(箭头函数中的this、super、arguments及new.target这些值由外围最近一层非箭头函数决定。)
    ·不能通过new关键字调用。(箭头函数没有【【Construct】】方法,所以不能做构造函数。)
    ·没有原型。
    ·不可以改变this的绑定。(函数内部的this值不可被改变,在函数的生命周期内始终保持一致,使用call(),apply(),bind()方法无效)
    ·不支持arguments对象。(函数箭头没有arguments绑定,所以必须通过命名参数和不定参数这两种形式访问函数的参数)
    ·不支持重复的命名参数。(无论在严格还是非严格模式下,箭头函数都不支持重复的命名参数)

    箭头函数语法
    let reflect=value=>value;
    //相当于
    let reflect=function(value){
        return value;
    };
    
    let sum=(num1,num2)=>num1+num2;
    //实际相当于
    let sum=function(num1.num2){
        return num1+num2;
    }
    
    let getName=()=>"cc"
    //相当于:
    let getName=function(){
        return "cc";
    }
    
    let sum=(num1,num2)=>{
        return num1+num2;
    };
    //实际上相当于
    let sum=function(num1,num2){
        return num1+num2;
    }
    
    let getTempItem=id=>({id:id,name:"changchang"});
    //相当于
    let getTempItem=function(id){
        return{
            id:id,
            name:"Temp"
        }
    }
    
    创建立即执行函数表达式
    let person=((name))=>{
        return{
            getName:function(){
                return name;
            }
        }
    }("changchang")
    console.log(person.getName());
    //等价于
    
    let person=function(name){
        return {
            getName:function(){
                return name;
            }
        };
    }("changchang");
    console.log(person.name());
    
    箭头函数没有this绑定
    let PageHandler={
        id:"123456",
        init:function(){
            document.addEventListener("click",function(event){
                this.doSomething(event.type);//抛出错误
            },false);
        },
        doSomething:function(type){
            console.log("Handing"+type+"for"+this.id)
        }
    };
    //这段代码没有按照效果实行,因为,this绑定的document。
    let PageHandler={
        id:"123456",
        init:function(){
            document.addEventListener("click",function(event){
                this.doSomething(event.type);
            }.bind(this),false);
        },
        doSomething:function(type){
            console.log("Handing"+type+"for"+this.id)
        }
    };
    //可以实现效果但实际上bind()创建了新函数,降低了效率。
    let PageHandler={
        id:"123456",
        init:function(){
            document.addEventListener("click",function(event){
                event=>this.doSomething(event.type);
            },false);
        },
        doSomething:function(type){
            console.log("Handing"+type+"for"+this.id)
        }
    };
    //ES6很好的实现了效果,this指向箭头函数外层this。
    

    箭头函数没有prototype属性。

    let MyTpe=()=>{},object=new MyTpe();//报错
    
    箭头函数和数组

    箭头函数的语法简洁,非常适用于数组处理。
    例如:简单的排序

    var result=values.sort(function(a,b){return a-b;});
    var result=values.sort((a,b)=>a-b);
    
    箭头函数没有arguments绑定

    箭头函数没有自己的arguments,且无论函数在哪个上下文执行,都可以访问外围的arguments对象。

    function createArrowFunctionReturningFirstArg(){
        return ()=>arguments[0];
    }
    var arrowFunction=createArrowFunctionReturningFirstArg(5);
    console.log(arrowFunction());//5
    
    尾调用优化

    ES5中,在循环调用中,每一个未用完的函数都会保存在内存中,当调用栈变得过大时会造成程序问题。
    ES6中缩短了严格模式下尾调用栈的大小(非严格模式不受影响),如果满足以下条件,尾调用将不再创建新的栈,而是清除并重用当前的栈。
    ·尾调用不访问当前栈的变量(也就是说函数不是一个闭包)。
    ·在函数内部,尾调用是最后一条语句。
    ·尾调用结果作为函数值返回。

    //例子
    "use strict"
    function doSomething(){
       //优化后
       return doSomethingElse();
    }
    //无法优化例子
    //First
    "use strict"
    function doSomething(){
       //无法优化,无返回
       doSomethingElse();
    }
    //无法优化例子
    //Second
    "use strict"
    function doSomething(){
       //无法优化,必须在返回值后添加其他操作
       return 1+doSomethingElse();
    }
    //无法优化例子
    //Third
    "use strict"
    function doSomething(){
       //无法优化,调用不在尾部
       var result=doSomethingElse();
       return result;
    }
    //无法优化例子
    //Fourth
    "use strict"
    function doSomething(){
        var num=1,func=()=>num;
       //无法优化,该函数是个闭包
       return func();
    }
    
    如何利用尾调用优化

    递归函数式主要应用场景,请看例子。

    function factorial(n){
        if(n<=1){
            return 1;
        }else{
            //无法优化,必须在返回后执行惩罚操作
            return n*factorial(n-1);
        }
    }
    
    function factorial(n,p=1){
        if(n<=1){
            return 1;
        }else{
            let result=n*p;
            //优化后
            return factorial(n-1,result);
        }
    }
    

    写递归函数时,利用好尾调用优化可以很好的提升程序性能。

    相关文章

      网友评论

        本文标题:ES6学习-函数

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