函数是所有编程语言的重要组成部分,在 ES6 出现前,JavaScript 的函数语法一直没有太大的变化 , 从而遗留了很多问题和隐晦的做法,导致一些基本功能经常要编写很多代码.例如定义一个类 要用到安全模式,做检测.函数默认参数如何赋值等问题.
JavaScript 函数有一个特别的地方,无论函数定义中声明了多少形参,都可以传入任意多个参数,由于这个原因,JavaScript函数有一个特殊的默认参数集合arguments,也是这个原因,JavaScript函数无法重载.这里讲述一下ES6 函数的默认参数的一些变化
function func1( a , b){
a = a || 200;
b = b || "argus2";
}
在这个实例中,为默认参数 a,b 赋默认值,通常使用逻辑或来实现,看上去没有任何问题,但还是有些问题,例如你无法给 a 赋值为0, 0转化为Boolean值为false ,所以 a 的 值还是200,同理你也无法给b赋值为一个空字符串.
为了解决这个问题,可以通过强制类型检测来实现,这也是大多数框架实现默认值的方法
function func1(a,b){
a = (typeof a !=="undefined") ? a :200;
b = (typeof b !=="undefined") ? b :"argus2";
}
ES6 实现默认参数的形式
function func1(a = 200 , b = "argus2"){
}
可以为函数指定任意多个参数指定默认值,在已指定默认值 的参数后面可以继续声明无默认值的参数 例如:
function func1( a , b=200,c){
//可以这样定义
}
且只有传入undefined或不填的时候,才会使用默认参数
function func1(a ,b=200){
console.log(b);
}
func1(1,null);//console.log(null);
在ES5 中使用严格模式 时, arguments 只读 ,非严格模式下,可写.
在ES6中使用参数默认值后 ,arguments 会和ES5 的严格模式下保持一致 –> 只读,不可写
关于默认参数值有一个有趣的特性,非原始值传参,例如
function getValue(){
return 5;
}
function add( first ,second = getValue()){
return first + second;
}
add 函数的第二个参数默认值为 getValue 函数的执行结果,但是getValue只有在第二个参数为默认参数的时候才会执行,并不会在声明的时候就执行,如果getValue()返回的是不同 值 , 就会出现 add (1);相同执行却返回不同值.
由于默认参数是在函数调用时求值,所以可以使用先定义的参数作为后定义的参数的默认值 ,例如:
function add(first ,second = first){
return first + second;
}
add(1,1);//2
add(1);//2
但是不能使用后定义的参数为先定义的参数的默认值,例如
function add(first = second,second){
return first + second;
}
add(undefined,1);//抛出错误
对于函数而言相当于执行
//换成传统参数默认值的做法,大致如下
var first = first || second;
//引用了second,但是second未定义,也就是临时锁区,报错
var second = second ;
在ES5 中有arguments ,无需定义每一个需要使用的参数.
在ES6 中 提供了不定参数
在函数的命名参数前添加三个点 (…)就表示这是一个不定参数,该参数为一个数组,包括着自它传入之后的所有参数,不定参数在函数定义中只能拥有一个,且必须作为最后一个参数,反过来,这其实就是解构赋值中的数组解构. 使用方法和规则一致
参考: 深入理解ES6 –解构
function func1(first,...argus){
console.log(argus);
}
func1(1,1,2,3,4);//[1,2,3,4]
let [first,...others] = [ 1, 1,2,3,4];
console.log(others);//[1,2,3,4]
ES6 中所有的函数的name 都有一个合适的值,直接定义的函数,name 属性值为函数名,
name 属性的特殊值,
var person = {
get firstName(){
return "Nicholas";
},
sayName:function(){
console.log(this.name);
}
}
console.log(person.sayName.name);//sayName
console.log(person.firstName.name);//get firstName
getter 和 setter 函数的name 属性会被加上前缀 get set,
通过bind 函数创建的函数其名称会添加”bound” 前缀 ,而通过Function构造函数创建的函数,其前缀 会添加 “anonymous”
在ES5中定义一个类 实际就是声明一个函数 ,使用new 关键词调用来实现,这样函数和类的双重身份会让人感到混乱.
JavaScript 函数有两个不同的内部方法 call 和construct,通过函数调用时,执行的是call ,而通过new 新建对象执行的是construct,拥有construct的内置方法的函数 ,称之为构造函数,由于不是所有的函数都有construct方法 ,例如箭头函数就没有.
在ES5 中为了安全的使用类 ,一般会使用一个称之为安全模式的方法
function Person(name,age){
if(this instanceof Person){
this.name = name;
this.age = age;
}else{
return new Person(name,age);
}
}
var person = new Person("lucy",20);//新建一个对象
var person1 = Person("jack",21);//一样新建了一个person对象
上述感觉可以解决函数是否只能被当做类调用的问题,但是有两种不依赖new 关键字就可以将this 绑定到person上面的实例,call 和apply,
var person = new Person("Jack",20);
var notPerson = Person.call(person,"Michale");
所以ES6 上做了些改进
为了解决这个问题,引入了new.target 这个元属性 , 当调用函数的 construct方法时,new.target被赋值为 new 操作符的目标.通常是新创建的对象实例,也就是函数体内this的构造函数,通过其他方式(call)调用的,则new.target 的值为undefined;
function Person(name,age){
if(new.target !=="undefined"){
this.name = name;
this.age = age;
}else{
console.error("必须通过new 关键词调用");
}
}
这样就解决了 通过call 和apply 来绑定this 的问题.
注意 new.target 只能在函数里面调用.
在ES6 中函数引入了一个有趣的新特性,箭头函数,箭头函数是一种使用箭头(=>)定义的新语法,与传统函数有些许不同
箭头函数的语法
var add = function(a,b){
return a+b;
}
var add1 = (a,b)=>a+b;
箭头函数的参数 为 =>左边括号的内容,右边为返回值.
当参数为一个的 时候()可以省略,当参数大于一个或者无参数时 “()” 是必须的.右边的为返回值,当返回的是一个对象时,返回值需要添加括号
//返回值为对象,需要添加括号
var createObj = ()=>({ name:"Jack",age:20 };
//复杂运算
var max = (a,b)=>{
if(a>b){
return a;
}
return b;
};
//上述可以简写为 var max = (a,b)=>a>b?a:b;
箭头函数没有this 绑定 ,很多时候为了调用最外层的this,在函数中会使用 一个变量来存储 this 值,或者使用bind(this),来解决这个问题.
例如:
//没有箭头函数前
let PageHandler = {
id : "12345",
init : function(){
document.addEventListener("click",(function(event){
this.doSomething(event.type);
}).bind(this),false);
},
doSomething:function(){
console.log(this.id);
}
}
//相同的代码,用箭头函数的效果
let PageHandler = {
id : "12345",
init : function(){
document.addEventListener("click",event=>this.doSomething(event.type),false);
},
doSomething:function(){
console.log(this.id);
}
}
箭头函数的语法很简洁,配合数组处理非常好用
例如排序,过滤等
var arr = [3,4,1,4];
arr.sort((a,b)=>a-b);//排序
arr.filter(a => a>3);//过滤
//还可以串联起来
var arr1 = [3,4,1,4];
arr1.sort((a,b)=>a-b).filter(a=>a<4);//[1,3]
由于箭头函数没有this绑定,所以使用call 或者apply 都无法改变箭头函数的this 引用.
尾调用优化是ES6引擎的优化, 之前的循环调用中,每一个未用完的栈帧都会被保存在内存中,当调用栈变得过大时.程序会出现问题,例如爆栈.
尾调用优化没有明确的语法,而是符合条件会自动优化,
1.严格模式下
2.尾调用不访问当前栈的变量(函数不能是一个闭包)
3.在函数内部,尾调用是最后一条语句
4.尾调用的结果作为函数值返回.
无法优化的示例
"use strict"
function doSomething(){
//调用不在尾部无法优化.
var Result = doSomethingElse();
return Result;
}
function doSomething2(){
var sum =1;
fun = () =>num;
//无法优化,闭包
return func();
}
尾调用优化 和函数的懒执行有异曲同工之妙,典型的案例就是斐波拉契数列. 把 时间复杂度从 N^2 下降到N.
本人才疏学浅,如有错漏,欢迎大家留言指正.讲解不周到之处也希望能和大家一起学习进步.