三 函数

196 阅读4分钟

默认参数

可以为函数参数设置默认值,这里当第二个参数不传,或者为undefined时取默认值:

function makeRequest(url,timeout=2000,callback){
}

默认参数对arguments的影响

arguments只与调用时传入的参数有关,不受参数默认值的影响。传入参数后arguments的值不会再改变,所以可以通过arguments将参数恢复为初始值。

function mixArgs(first,second=2){
    console.log(first===arguments[0]);  //true
    console.log(second===arguments[1]); //false
    console.log(arguments[1]); //undefined
    first=2;
    console.log(arguments[0]); //1
}
mixArgs(1);  

默认参数表达式

参数的默认值,可以是表达式。
默认参数求值,是在函数被调用时才求值,所以后一个参数的默认值可以等于前一个参数。

function foo(first,second=first,thrid=getValue()){
    
}

但前一个参数的默认值不能是后一个参数。其原因是因为函数参数是在临时死区中,有自己的作用域(不能访问函数体的参数),未执行声明语句前不能进行访问,访问会报错。执行以下函数时

function erroFoo(first=second,second){
    
}

执行erroFoo(undefined,1)时,其实是经过以下步骤:

let first=second;   //在second声明未执行时访问,报错。  
let second=1;

不定参数——处理无名参数

语法如下:

function foo(book,...keys){
    
}

这里keys为不定参数代表所有无名参数组成的数组。

不定参数使用限制:

  • 每个函数最多只有一个不定参数,并且只能在末尾。
  • 不能用于对象字面量的setter中。

不定参数对arguments和length无影响。

增强的Function构造函数

可以在Function构造函数中使用默认参数和不定参数。如下:

var foo=new Function("first","second=first","...args","return first+second+args[0]")

展开运算符

前面把...放在函数的参数中,是把传入的无名参数放入一个不定参数数组中。而把...放在调用函数的地方,则是把数组打散成一个个参数传入。

var value=[1,2,3,4,8,9];

//等价于
//Math.max(1,2,3,4,8,9,0)
console.log(Math.max(...value,0)); //9,这里展示了展开运算符和正常传入的参数混合使用,这是apply()无法做到的。

name属性

用来方便调试的,并且函数声明比函数表达式的名字权重高。
用Function构造函数时,前缀为"anoymous";
用bind()构建的函数,前缀为"bound"

函数的多重用途

es6的函数内部有[[Call]]和[[Construct]]双重方法。通过new时,调用的是[[Construct]]。不是所有的函数都有[[Construct]]方法的,箭头函数就没有,没有的不能通过new调用。

元属性new.target

元属性:非对象的属性。

当使用new调用构造函数时,new.target为当前实例的构造函数。不用new调用时为undefined。

在函数外使用new.target会报语法错误。

可以判断new.target===Pers代替之前的this intstanceof Pers:

function Pers(name){
    if(new.target===Pers){
        this.name=name;
    }
    else{
        throw new Error("必须通过new调用");
    }
}
var pr1=Pers("xiao"); //报错

块级函数

在代码块内:严格模式下声明的函数,会被提升至块级作用域顶部,并且与let表达式一样,块级代码执行完就销毁;非严格模式下,会被提升至外围函数作用域或者全局作用域顶部。(虽然有块级作用域,但只有let和const能体现,而这里也体现了es6的块级作用域,es5严格模式下不能在块级作用域声明函数,只能用表达式)。

"use strict"
sayName(); //报错
if(true){
    sayName(); //可以
    function sayName(){
        alert("nihao");
    }
}

箭头函数

  • 没有argumets对象,所有使用时会通过作用域链找到外层非箭头函数的argums。可以用不定参数代替;
  • 没有 [[construct]],不能通过new调用,也没new.target;
  • 没有原型,即没有prototype属性;
  • this始终是外层非箭头函数的this,没有外层非箭头函数则为window,不能改变this绑定;
  • 不支持重复命名参数。

具体使用方式可以看P60。
箭头函数创建立即执行函数表达式时,小括号只包含函数,不包含调用的括号。非箭头函数没有这个限制。如下

(name=>{
 console.log(name);
})("xiao");

适用场景:即用即弃,回调函数、匿名函数表达式,setTimeout()、事件、数组的迭代、归并方法、sort等。

可以使用typeof、instanceof测定箭头函数,和普通函数没什么区别。

尾调用优化

帮助函数保持一个更小的调用栈,从而减少内存使用,避免栈溢出错误。

可以尾调用优化的条件:

  • 尾调用函数不是一个闭包
  • 尾调用的结果作为值返回,没有任何修饰

尾调用优化常用于递归。如下代码可以优化阶乘函数性能:

//阶乘函数
 function factorial(n,p=1){
    if(n<=1){
        return 1*p;
    }
    else{
        p=p*n;
        return factorial(n-1,p); //这步体现了尾调用优化
    }
 }
 console.log(factorial(4)); //24