浅谈ES6中的箭头函数

13 篇文章 2 订阅
2 篇文章 0 订阅

       ES6中提出了一个新的概念,叫做箭头函数。我们可以把这个概念,理解成一个“简化版”的函数。之所以说箭头函数是所谓的“简化版”,主要原因有二,其一是箭头函数的写法比较简单,其二是箭头函数缺少Function所具备的一些“配套设施”,它们包括:this、super、arguments对象和new.target。今天我们就来着重地讨论一下箭头函数。

  基本用法

     首先看一段代码:

// 普通函数版本
var f1 = function(v){
    return v;
}
//箭头函数版本
var f2 = v => v;
     上例中的f1和f2起到的作用是相同的,都是原样返回输入参数v。我们可以看到,箭头函数的写法更加简洁,且语义更加清晰。

      上面的例子过于简单,我们来看一下稍微复杂点的例子:

// 普通函数版本
var number = function(n1,n2,n3){
    var a = n1 + n2;
    var b = a * n3;
    return b;
}

// 箭头函数版本
var number = (n1,n2,n3)=> {  
     var a = n1 + n2;  
     return a * n3;  
}
     在这个例子里面,我们会看出:

  •  箭头函数的箭头右侧,不仅可以是变量,还可以是代码块。
  •  箭头函数右侧的代码块中,需要用return语句显式的声明返回值。
  •  在箭头的右侧的代码块中,如果只有一条语句,那么代码块的大括号({})和return语句都可以被省略。
  •  代码块同ECMAScript 标准的其他位置一样,使用大括号({}),包括起来的。
     看到这里,细心的开发者会有个疑惑,如果箭头函数右侧要返回一个对象的话,是如何书写的?毕竟对象字面量的写法也是用大括号包含的,答案是在大括号外边,再加一个括号。
var person1 = (name,age)=>({ name : name, age :age });   
     长期用面向对象的形式编程的开发者都知道,函数内部是可以“尾部调用”的,即是说函数可以不return值,而是以调用其他函数的方式结尾。对于这种需求,箭头函数的写法如下:
// 箭头函数版本
// 此处的参数,用了rest参数的写法
let result = (...args) => doSomethings(...args);

// 普通函数
let result2 = function(...args){
    doSomethings(...args);
}
     在上面的例子里面,我们不止看到了尾部调用的写法,还看到了rest形式的参数,我们知道,rest参数是不需要限定参数个数的,那么再想的深一层,如果,不需要输入参数的话,箭头函数怎么写?
var nopra = () => "没有参数,返回此处字符串,nopra的值就是这个字符串"
     箭头函数是可以嵌套箭头函数的,看下面的例子1:
     
 function insert(value){
    return {
       into : function(array){
           return {
               after : function(afterValue){
                   array.splice(array.indexOf(afterValue) + 1,0,value);
                   return array;   
               }
           }       
       }
    }
 }
     上面的这个例子,让我们可以在一个数组中的某个值后面上插入一个值。使用方法如下:
insert(2)into([1,3]).after(1);
// 得到结果:[1,2,3]
     如果用箭头函数的方法来写这个例子的话,写法如下:
let insert = (value) => ({
   into : (array) => ({
       after : (afterValue) => {
           array.splice(array.indexOf(afterValue) + 1,0,value)
       }
   });
})
     经过例子1的对比后,我们又一次的发现箭头函数写的代码比较精简。下面是一个管道机制的代码例子,所谓的管道机制是指,前一个代码的输出是后一个代码的输入。例子2代码如下:
// 管道函数pipeline,参数采用解构赋值,在函数内部会以数组的形式存在。
const pipeline = (...funcs) => 
     val => funcs.reduce((a,b) => b(a),val);  //a和b是指输入参数中相邻的两个函数,限制性a然后将结果传递给b执行,val是初始化的值,就是下面addThenMulti(5)中的5。
// 箭头函数:参数加一
const plus1 = a => a+1;
// 箭头函数:参数乘二
const mult2 = a => a*2;
// 将两个箭头函数传递给pipeline,形成通道
const addThenMult = pipeline(plus1,mult2);
// 初始化值是5,经过通道后的结果是12
addThenMulti(5);
// 结果是:12
    上面的写法,可读性不太好,如果不考虑通道的话,其实效果和下面的函数相同
// 没有写通道函数
const plus1 = a => a+1;
const mult2 = a => a*2;
// hardcode 将两个函数串联。
mult2(plus(1));
// 12
      箭头函数还有一个重要的作用是,可以很方便的书写 “ λ 演算。关于 “λ 演算”的具体内容,我在此不详细阐述了,这是一个数学的概念,在计算机领域里应用很广。在这里只给一个例子,想要详细了解的同学,请自行查找相关资料。
// λ演算的写法
fix = λf.(λx.f(λv.x(x)(v)))(λx.f(λv.x(x)(v)))

//ES6 的写法
var fix = f => ( x => f( v => x(x)(v)))( x=> f( v => x(x)(v)));

箭头函数与普通函数的不同

开篇的时候,我们就提到了,箭头函数就像是一个“简化版”的不同之处在于:

  • 箭头函数没有this指针
  • 箭头函数没有super指针
  • 箭头函数没有arguments对象
  • 箭头函数没有new.target对象(new.target属性允许你检测函数或构造方法是否是通过new运算符被调用的。在通过new运算符被初始化的函数或构造方法中,new.target返回一个指向构造方法或函数的引用。在普通的函数调用中,new.target 的值是undefined

       这里重点说一下箭头函数没有this指针引发的一系列连锁反应:

       首先,没有this指针,就代表着,箭头函数无法成为构造函数,无法被new命令调用。这里的理由很简单,举例而言:

function A(){
   this.prop1 = 1;
   this.prop2 = "属性2"
   this.prop3 = function(){
      console.log("这是一个保护方法");
   }
}
//实例化构造函数A
var a = new A();
     上面的例子,是在class功能出现之前,常见的面向对象的JS写法,构造函数A中的this指向new命令新建的对象,this函数称之为构造函数的“必要条件”,所以缺少this的箭头函数无法成为“构造函数”。

      其次,由于super的设计初衷是,this指向对象的“原型对象”,所以缺乏this的话,同样会缺乏super。

      再次,由于箭头函数没有自己的this,所以,在函数内部代码中,写的this都默认是箭头函数所在代码块的this。这导致了一个很新奇的现象:普通函数的this指向的是函数运行时,调用函数的对象。箭头函数中的this(即所在代码块的this)指向的是箭头函数定义时所在的对象

function foo1() {  
  console.log("foo1箭头函数外this指向对象的ID:"+this.id);  
  setTimeout(() => {  
    console.log("箭头函数内this指向对象的ID:"+ this.id);  
  }, 1000);  
}  
function foo2(){  
   console.log("foo2普通函数外this指向对象的ID:"+this.id);  
   setTimeout(function(){  
    console.log("普通函数内this指向对象的ID:"+ this.id);  
   }, 1000);  
}  
  
window.id = "window";  
foo1.call({ id: "obj"})  
foo2.call({ id: "obj" }); 
     上面代码的运行结果是:
> "foo1箭头函数外this指向对象的ID:obj"  
> "foo2普通函数外this指向对象的ID:obj"  
> "箭头函数内this指向对象的ID:obj"  
> "普通函数内this指向对象的ID:window" 

     这个例子进一步阐明了箭头函数中this指向。在这里解释一下上面的代码。

  1. 首先,我们用call方法,将Id为“obj”的对象的作用域,传递到foo1和foo2方法的this中,所以,在setTimeout外面,的console中,this指向的都是obj。
  2. 其次,在setTimeout中,普通函数的this,指向了函数运行时的上下文(window);
  3. 再次,在setTimeout中,箭头函数中的this,指向的是函数定义时的上下文(Obj对象)。
      综上所述,foo1.call和foo2.call产生了不同的运行结果。
      在举一个例子:
function foo(){  
    return ()=>{  
        return ()=>{  
           return ()=>{  
               console.log('id:',this.id);  
           };  
        };  
    };  
}  
var f = foo.call({id:1});  
  
var t1 = f.call({id:2})()();  //id:1  
var t2 = f().call({id:3})();  //id:1  
var t3 = f()().call({id:4});  //id:1 
     在上面代码中,只有一个this,就是函数的this,所以t1,t2,t3都输出了同样的结果,因为所有的内层函数都是箭头函数,都没有自己的this,它们的this都是最外层函数foo的this。
     除了this之外,arguments,super,new.target也是不存在的。它们都会向this一样指向外层函数的对应变量。

     最后提及一下,由于缺少this,所以call()、bind()和call()方法不能用改变箭头函数的指向。

       

   
      

      

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值