前言箭头函数是ES6新增的语法。它简化了我们的代码,让开发者摆脱了“飘忽不定”这一点。深受开发者的喜爱,在采访中也很受欢迎。官方的最爱,箭头函数因为它不同于普通函数的特点,经常出现在各大公司的面试题中,所以,本文将对箭头函数和普通函数进行一些分析。如果本文对你有帮助,??关注+点赞??给作者打气,文章公众号首发,关注前端南九第一时间获取最新文章~箭头函数(ArrowFunction)介绍ES6允许使用“箭头”(=>)来定义函数。箭头函数等同于匿名函数,简化了函数定义。让我们看看如何使用(=>)来声明一个函数://箭头函数letfoo=(name)=>`Iam${name}`foo('NanJiu')//IamNanJiu//它相当于下面的普通函数letfoo2=function(name){return`Iam${name}`}箭头函数有两种格式,一种和上面一样,只包含一个表达式,甚至是{...}和The返回被省略。还有另一种可以包含多个语句。这时候{...}和return不能省略:letfoo=(name)=>{if(name){return`我是${name}`}return'前端南玖'}foo('南久')//我是南久??这里需要注意的是,如果箭头函数返回的是字面量对象,需要把字面量对象用括号括起来,然后返回letfoo=(name)=>({name,job:'frontend'})//相当于letfoo2=function(name){return{name,job:'frontend'}}OK,箭头函数的基本介绍我们先看这里,Next,我们将通过比较箭头函数与普通函数的区别~箭头函数与普通函数的区别来进一步了解箭头函数。我们可以打印箭头函数和普通函数,看看两者的区别:letfn=name=>{console.log(name)}letfn2=function(name){console.log(name)}console.dir(fn)//console.dir(fn2)//从打印结果来看,箭头函数与普通函数类似,相比于缺少caller、arguments、prototype的不同声明方式,匿名函数声明了一个普通函数function使用关键字function来完成,usingfunction可以声明为命名函数或匿名函数。声明箭头函数时,只需使用箭头即可,无需使用关键字function,比普通函数声明更简洁。箭头函数只能声明为匿名函数,但可以使用表达式使箭头函数以不同的方式命名这一点。对于普通函数,内部的this指向函数运行的对象,但是对于箭头函数则不然。它没有自己的this对象,内部的this就是定义时上层作用域中的this。也就是说,箭头函数内部的this指针是固定的,相比之下,普通函数的this指针是可变的。varname='nanjiu'varperson={name:'nanjiu',say:function(){console.log('say:',this.name)},say2:()=>{console.log('say2:',this.name)}}person.say()//say:nanjiuperson.say2()//say2:南久的第一个say在这里定义了一个普通的函数,调用的是对象person的方法,所以其this指向person,所以它应该输出say:nanjiu,第二个say2定义了一个箭头函数。我们知道箭头函数本身是没有this的,它定义的时候this总是指向上层作用域,所以say2的this应该指向全局窗口,所以会输出say2:Nanjiu我们可以同样用Babel生成的ES5代码对箭头函数进行转换,证明箭头函数没有自己的this,而是在上层作用域引用了this。//ES6functionfoo(){setTimeout(()=>{console.log('id:',this.id);},100);}//ES5functionfoo(){var_this=this;setTimeout(function(){console.log('id:',_this.id);},100);}转换后的ES5版本很明显箭头函数里根本没有this,但是上层作用域里有this的参考。箭头函数的this永远不会改变,也不能调用、应用或绑定。我们可以用call、apply、bind来改变一个普通函数的this点,但是由于箭头函数的this点在定义的时候就已经确定了,所以永远不会改变。它在定义时指向上层作用域中的this,所以使用这些方法永远不会改变箭头函数this的指向。varname='nanjiu'varperson={name:'nanjiu',say:function(){console.log('say:',this.name)},say2:()=>{console.log('say2:',this.name)}}person.say.call({name:'小明'})//say:小明person.say2.call({name:'小红'})//say2:南九是还是上面的例子,只不过我们在调用的时候用call来尝试改变this的方向。第一个说的是一个普通的函数。通过call调用,打印出say:XiaoMing,也就是说普通函数的this发生了变化,第二个say2是箭头函数,也是call调用的,但是还是打印say2:Nanjiu,证明箭头函数的this永远不会变,即使你用call,apply,bind。将箭头函数更改为无原型letfn=name=>{console.log(name)}letfn2=function(name){console.log(name)}console.log(fn.prototype)//undefinedconsole.dir(fn2.prototype)//{constructor:?}箭头函数不能用作构造函数为什么箭头函数不能用作构造函数?我们先用new调用看看会发生什么:letfn=name=>{console.log(name)}constf=newfn('nanjiu')结果符合我们的预期,所以调用会报错我们知道new内部实现其实分为以下四步:创建一个新的空对象链接原型绑定this,执行构造函数返回新对象functionmyNew(){//1.创建一个新的空对象letobj={}//2.获取构造函数letcon=arguments.__proto__.constructor//3.链接原型obj.__proto__=con.prototype//4.绑定是,执行构造函数letres=con.apply(obj,arguments)//5.返回一个新对象returntypeofres==='object'?res:obj}因为箭头函数没有自己的this,它的this其实是继承了外部执行环境中的this,this永远不会变,而且箭头函数没有prototype原型,__proto__属性其实例的of不能被指向,所以箭头函数不能作为构造函数使用,否则使用new调用时会报错!没有new.targetnew是从构造函数生成实例对象的命令。ES6为新命令引入了new.target属性。该属性一般用在构造函数中,返回new调用的构造函数。如果构造函数不是通过new命令或Reflect.construct()调用的,new.target将返回undefined,所以这个属性可以用来确定构造函数是如何被调用的。functionfn(name){console.log('fn:',new.target)}fn('nanjiu')//undefinednewfn('nanjiu')/*fn:fn(name){console.log('fn:',new.target)}*/letfn2=(name)=>{console.log('fn2',new.target)}fn2('nan')//未捕获的语法错误:不允许使用new.target表达式这里??注意:new.target属性一般用在构造函数中,返回new调用的构造函数的箭头函数的this指向全局对象。在箭头函数中使用new.target会报错。箭头函数的this指向普通函数。它的new.target是指向普通函数的引用。箭头函数没有自己的参数。箭头函数在全局范围内,所以没有参数letfn=name=>{console.log(arguments)}letfn2=function(name){console.log(arguments)}fn2()//Arguments[callee:?,Symbol(Symbol.iterator):?]fn()//报UncaughtReferenceError:argumentsisnotdefined或者用这两个函数比较,通常函数可以打印出arguments,箭头函数会报错如果它使用参数,因为箭头函数本身没有参数,然后它会在上层范围内搜索参数。由于参数未在全局范围内定义,因此会报错。箭头函数在普通函数的函数作用域内,参数为上层普通函数的参数letfn2=function(name){console.log('fn2:',arguments)letfn=name=>{console.log('fn:',arguments)}fn()}fn2('nanjiu')这里两个函数打印的参数是一样的,都是fn2函数的参数。可以使用rest参数代替ES6引入rest参数,rest参数用于为函数获取可变数量的参数数组,此API用于替换arguments,形式为...变量名,变量与rest参数匹配的是一个数组,变量将多余的参数放入数组中。让fn3=(a,...arr)=>{console.log(a,arr)//1,[2,3,4,5,6]}fn3(1,2,3,4,5,6)以上是rest参数的基本用法,需要注意:rest参数只能作为函数的最后一个参数//errorfunctionf(a,...b,c){//...}function长度属性不包括剩余参数。rest参数和arguments的比较:箭头函数和普通函数都可以使用rest参数,arguments只能被普通函数使用。休息比参数更灵活。剩余参数是一个真正的数组,arguments是一个类数组对象,不能直接使用数组方法箭头函数不能重复函数参数名functionfn(name,name){console.log('fn2:',name)}letfn2=(姓名,姓名)=>{控制台。log('fn',name)}fn('nan','jiu')//'jiu'fn2('nan','jiu')//yield命令不能用于报错,所以箭头函数不能用作生成器函数。这可能是由于历史原因。TC39在2013年和2016年讨论过两次,从*(),*=>,=*>,=>*中选出=>*,勉强进入stage1。而因为有了异步生成器(asyncgenerator),你不得不同时考虑异步箭头生成器(asyncarrowgenerator)。之前99.999%的generator都是用来实现异步编程的,其实并不是很需要。generator的初衷,自从有了async/await,generator生成器就越来越少用了。猜测可能是出于这个原因,添加不常用的语法可能不值得为规范增加复杂性。箭头函数不适用于场景对象方法,thisvarname='nanjiu'varperson={name:'nanjiu',say:function(){console.log('say:',this.name)}中使用themethod,say2:()=>{console.log('say2:',this.name)}}person.say()//say:nanjiuperson.say2()//say2:在上面的南九代码中,person.say2()方法是一个箭头函数。调用person.say2()时,this会指向全局对象,所以得不到预期的结果。这是因为对象不构成单独的作用域,所以say2()箭头函数定义的作用域是全局作用域。而say()定义了一个普通的函数,其内部的this指向了调用它的对象,所以使用一个普通的函数也是意料之中的。当函数需要动态时thisvarbutton=document.querySelector('.btn');button.addEventListener('click',()=>{this.classList.toggle('on');});这里很明显报错了,因为按钮点击的回调是一个箭头函数,而箭头函数里面的this一直指向它上层作用域的this,这里就是window,所以会报错。这里只需要将箭头函数改成普通函数就可以正常调用了!看完我们做一道题~varname='南九'functionPerson(name){this.name=namethis.foo1=function(){console.log(this.name)},this.foo2=()=>console.log(this.name),this.foo3=function(){returnfunction(){console.log(this.name)}},this.foo4=function(){return()=>{console.log(this.name)}}}varperson1=newPerson('nan')varperson2=newPerson('jiu')person1.foo1()//'nan'person1.foo1.call(person2)//'jiu'person1.foo2()//'nan'person1.foo2.call(person2)//'nan'person1.foo3()()//'nanjiu'person1.foo3.call(person2)()//'南玖'person1.foo3().call(person2)//'jiu'person1.foo4()()//'nan'person1.foo4.call(person2)()//'jiu'person1.foo4().call(person2)//'nan'分析:全局代码执行,person1=newPerson('nan'),person2=newPerson('jiu')被执行,person1中的this.name为nan,person2中的this.name是jiu,明白这一点后就OK了,继续往下看:执行person1.foo1(),foo1是一个普通的函数,所以这个应该指向person1,打印出nan并执行person1.foo1.call(person2),foo1是一个普通函数,并且并且用call改变了this的方向,所以里面的this应该指向person2,打印出jiu并执行person1.foo2(),foo2是一个箭头函数,它的this指向上层作用域,也就是person1,所以打印出nan并执行person1.foo2.call(person2),箭头函数的this点不能被调用改变,所以它的this仍然指向person1,打印出nan并执行person1.foo3()(),这里先执行person1.foo3(),返回一个普通函数,然后执行这个函数,此时相当于在全局范围内执行一个普通函数,所以它的this指向window,打印出南九执行person1.foo3.call(person2)(),和上面类似,也是返回一个普通的函数,然后执行。其实你不需要关心之前的执行情况。相当于在全局范围内执行一个普通函数,所以它的this指向window,打印出南九执行person1.foo3().call(person2)这里是绑定foo3返回的普通函数的this给person2,所以打印出jiu,执行person1.foo4()(),先执行person1.foo4()返回一个箭头函数,然后Execute这个箭头函数。由于箭头函数的this始终指向它的上层作用域,所以它打印出nan并执行person1.foo4.call(person2)(),和上面类似但是使用call将上层作用域的this改为person2,所以打印出jiu,执行person1.foo4().call(person2),这里是先执行person1.foo4(),返回箭头函数,然后尝试通过call改变箭头函数的this点,我们上面说箭头函数的this一直指向它的上层作用域,所以它打印出了nan。推荐阅读前端常见安全问题及防范措施。介绍回流&重绘(Reflow&Repaint),以及如何优化?Promise、Generator和Async之间有什么区别?JS定时器执行不可靠的原因及解决方案从如何使用到如何实现一个Promise页面加载过程超详解原文首发请点击这里,欢迎大家关注!!!
