Web前端之路计算机微刊前端程序员干货

ES6学习-函数

2017-09-20  本文已影响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);
    }
}

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

上一篇下一篇

猜你喜欢

热点阅读