函数形参的默认值
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);
}
}
写递归函数时,利用好尾调用优化可以很好的提升程序性能。
网友评论