深入理解ES6 -- 函数

前言

函数是所有编程语言的重要组成部分,在 ES6 出现前,JavaScript 的函数语法一直没有太大的变化 , 从而遗留了很多问题和隐晦的做法,导致一些基本功能经常要编写很多代码.例如定义一个类 要用到安全模式,做检测.函数默认参数如何赋值等问题.

1.函数形参的默认参数

JavaScript 函数有一个特别的地方,无论函数定义中声明了多少形参,都可以传入任意多个参数,由于这个原因,JavaScript函数有一个特殊的默认参数集合arguments,也是这个原因,JavaScript函数无法重载.这里讲述一下ES6 函数的默认参数的一些变化

  • 默认参数
    在ES5 中使用默认参数常用方法
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);

使用参数默认值对 arguments 的影响

在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]

name 属性

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

为了解决这个问题,引入了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 中函数引入了一个有趣的新特性,箭头函数,箭头函数是一种使用箭头(=>)定义的新语法,与传统函数有些许不同

  1. 没有this,super,arguments 和new.target绑定,箭头函数中的这些值都是由外围最近一层的非箭头函数决定的.
  2. 不能通过new 关键字调用,也就是说箭头函数没有内置 construct 方法
  3. 没有原型,不能通过new 关键字调用,就不能成为构建原型的需求,所以 没有prototype 属性. 不能被定义为一个类,一个纯粹的函数
  4. 不可以改变this 的绑定,函数里面this 值不可改变,始终保持一致
  5. 不支持arguments对象 ,但是可以通过不定参数和默认参数来访问参数列表,
  6. 不支持重复的命名参数

箭头函数的语法

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.

本人才疏学浅,如有错漏,欢迎大家留言指正.讲解不周到之处也希望能和大家一起学习进步.

你可能感兴趣的:(js,深入理解ES6)