箭头函数作为ES6中新加入的语法,以其简化了我们的代码和让开发人员摆脱了“飘忽不定”的this指向等特点,深受广大开发者的喜爱,同时也深受面试官的喜爱,箭头函数常因其不同于普通函数的特点出现在各大公司的面试题中,so,本文会对箭头函数与普通函数进行一些分析。
如果这篇文章有帮助到你,??关注+点赞??鼓励一下作者,文章公众号首发,关注 前端南玖 第一时间获取最新的文章~
ES6中允许使用“箭头”(=>) 来定义函数。箭头函数相当于匿名函数,并且简化了函数定义。
我们来看一下如何使用 (=>) 来声明一个函数:
// 箭头函数let foo = (name) => `我是${name}`foo('南玖') // 我是南玖// 等同于下面这个普通函数let foo2 = function(name) { return `我是${name}`}箭头函数有两种格式,一种像上面的,只包含一个表达式,连{ ... }和return都省略掉了。还有一种可以包含多条语句,这时候就不能省略{ ... }和return:
let foo = (name) => { if(name){ return `我是${name}` } return '前端南玖'}foo('南玖') // 我是南玖??这里需要注意的是如果箭头函数返回的是一个字面量对象,则需要用括号包裹该字面量对象返回
let foo = (name) => ({ name, job: 'front end'})// 等同于let foo2 = function (name) { return { name, job: 'front end' }}OK,箭头函数的基本介绍我们先看到这里,下面我们通过对比箭头函数与普通函数的区别来进一步了解箭头函数~
我们可以通过打印箭头函数和普通函数来看看两者到底有什么区别:
let fn = name => { console.log(name)}let fn2 = function(name) { console.log(name)}console.dir(fn) // console.dir(fn2) // 从打印结果来看,箭头函数与普通函数相比,缺少了caller,arguments,prototype
function来完成,并且使用function既可以声明成一个具名函数也可以声明成一个匿名函数function,比普通函数声明更简洁。对于普通函数来说,内部的this指向函数运行时所在的对象,但是这一点对箭头函数不成立。它没有自己的this对象,内部的this就是定义时上层作用域中的this。也就是说,箭头函数内部的this指向是固定的,相比之下,普通函数的this指向是可变的。
var name = '南玖'var person = { 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应该指向的是全局window,所以它会输出say2: 南玖
我们也可以通过Babel 转箭头函数产生的 ES5 代码来证明箭头函数没有自己的this,而是引用的上层作用域中this。
// ES6function foo() { setTimeout(() => { console.log('id:', this.id); }, 100);}// ES5function foo() { var _this = this; setTimeout(function () { console.log('id:', _this.id); }, 100);}转换后的 ES5 版本清楚地说明了,箭头函数里面根本没有自己的this,而是引用的上层作用域中this。
我们可以用call、apply、bind来改变普通函数的this指向,但是由于箭头函数的this指向在它定义时就已经确定了,永远指向它定义时的上层作用域中的this,所以使用这些方法永远也改变不了箭头函数this的指向。
var name = '南玖'var person = { 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指向,第一个say是一个普通函数,它经过call调用,打印出的是say: 小明,这说明普通函数的this已经改变了,第二个say2是一个箭头函数,它也经过call调用,但它打印出的仍然是say2: 南玖,这就能够证明箭头函数的this永远不会变,即使使用call、apply、bind也无法改变
let fn = name => { console.log(name)}let fn2 = function(name) { console.log(name)}console.log(fn.prototype) // undefinedconsole.dir(fn2.prototype) // {constructor: ?}为什么箭头函数不能当成一个构造函数呢?我们先来用new调用一下看看会发生什么:
let fn = name => { console.log(name)}const f = new fn('nanjiu')结果符合我们的预期,这样调用会报错
我们知道new内部实现其实是分为以下四步:
新建一个空对象
链接到原型
绑定this,执行构造函数
返回新对象
function myNew() {// 1.新建一个空对象let obj = {}// 2.获得构造函数let con = arguments.__proto__.constructor// 3.链接原型obj.__proto__ = con.prototype// 4.绑定this,执行构造函数let res = con.apply(obj, arguments)// 5.返回新对象return typeof res === 'object' ? res : obj}因为箭头函数没有自己的this,它的this其实是继承了外层执行环境中的this,且this指向永远不会变,并且箭头函数没有原型prototype,没法让他的实例的__proto__属性指向,所以箭头函数也就无法作为构造函数,否则用new调用时会报错!
new是从构造函数生成实例对象的命令。ES6 为new命令引入了一个new.target属性,这个属性一般用在构造函数中,返回new调用的那个构造函数。如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined,所以这个属性可以用来确定构造函数是怎么调用的。
function fn(name) { console.log('fn:',new.target)}fn('nanjiu') // undefinednew fn('nanjiu') /*fn: ? fn(name) { console.log('fn:',new.target)}*/let fn2 = (name) => { console.log('fn2',new.target)}fn2('nan') // 报错 Uncaught SyntaxError: new.target expression is not allowed here??注意:
new.target属性一般用在构造函数中,返回new调用的那个构造函数
箭头函数的this指向全局对象,在箭头函数中使用new.target会报错
箭头函数的this指向普通函数,它的new.target就是指向该普通函数的引用
let fn = name => { console.log(arguments)}let fn2 = function(name) { console.log(arguments)}fn2() // Arguments [callee: ?, Symbol(Symbol.iterator): ?]fn() // 报错 Uncaught ReferenceError: arguments is not defined还是用这两个函数来比较,普通函数能够打印出arguments,箭头函数使用arguments则会报错,因为箭头函数自身是没有arguments的,然后它会往上层作用域中去查找arguments,由于全局作用域中并没有定义arguments,所以会报错。
let fn2 = function(name) { console.log('fn2:',arguments) let fn = name => { console.log('fn:',arguments) } fn()}fn2('nanjiu')这里两个函数打印的arguments相同,都是fn2函数的arguments
ES6 引入 rest 参数,用于获取函数不定数量的参数数组,这个API是用来替代arguments的,形式为...变量名,rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
let fn3 = (a,...arr) => { console.log(a,arr) //1, [2,3,4,5,6]}fn3(1,2,3,4,5,6)上面就是rest参数的基本用法,需要??注意的是:
rest参数只能作为函数的最后一个参数// 报错function f(a, ...b, c) { // ...}length属性,不包括rest参数rest参数与arguments的比较:
rest参数,而arguments只能普通函数使用rest比arguments更加灵活rest参数是一个真正的数组,而arguments是一个类数组对象,不能直接使用数组方法function fn(name,name) { console.log('fn2:',name)}let fn2 = (name,name) => { console.log('fn',name)}fn('nan','jiu') // 'jiu'fn2('nan','jiu') // 报错yield命令,因此箭头函数不能用作 Generator 函数。这个可能是由于历史原因哈,TC39 在 2013 年和 2016 年分别讨论过两次,从*()、*=>、=*>、=>* 中选出了=>*,勉强进入了 stage 1。而且因为有了异步生成器(async generator),所以还得同时考虑异步箭头生成器(async arrow generator)的东西,之前生成器 99.999% 的用途都是拿它来实现异步编程,并不是真的需要生成器本来的用途,自从有了 async/await,generator生成器越来越没人用了。猜测可能是因为这个原因添加一个使用频率不高的语法,给规范带来较大的复杂度可能不值当。
var name = '南玖'var person = { name: 'nanjiu', say: function() { console.log('say:',this.name) }, say2: () => { console.log('say2:',this.name) }}person.say() // say: nanjiuperson.say2() //say2: 南玖上面代码中,person.say2()方法是一个箭头函数,调用person.say2()时,使得this指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致say2()箭头函数定义时的作用域就是全局作用域。而say()定义的是一个普通函数,它内部的this就指向调用它的那个对象,所以使用普通函数符合预期。
var button = document.querySelector('.btn');button.addEventListener('click', () => { this.classList.toggle('on');});这里很显然会报错,因为按钮点击的回调是一个箭头函数,而箭头函数内部的this永远都是指向它的上层作用域中的this,在这里就是window,所以会报错。这里只需要将箭头函数改成普通函数就能正常调用了!
var name = '南玖'function Person (name) { this.name = name this.foo1 = function () { console.log(this.name) }, this.foo2 = () => console.log(this.name), this.foo3 = function () { return function () { console.log(this.name) } }, this.foo4 = function () { return () => { console.log(this.name) } }}var person1 = new Person('nan')var person2 = new Person('jiu')person1.foo1() // 'nan'person1.foo1.call(person2) // 'jiu'person1.foo2() // 'nan'person1.foo2.call(person2) // 'nan'person1.foo3()() // '南玖'person1.foo3.call(person2)() // '南玖'person1.foo3().call(person2) // 'jiu'person1.foo4()() // 'nan'person1.foo4.call(person2)() // 'jiu'person1.foo4().call(person2) // 'nan'解析:
全局代码执行,person1 = new Person('nan'),person2 = new Person('jiu')执行完,person1中的this.name为nan,person2中的this.name为jiu,OK这一点清楚后,继续往下看:
person1.foo1(),foo1为普通函数,所以this应该指向person1,打印出nanperson1.foo1.call(person2),foo1为普通函数,并且用call改变了this指向,所以它里面的this应该指向person2,打印出jiuperson1.foo2(),foo2为箭头函数,它的this指向上层作用域,也就是person1,所以打印出nanperson1.foo2.call(person2),箭头函数的this指向无法使用call改变,所以它的this还是指向person1,打印出nanperson1.foo3()(),这里先执行person1.foo3(),它返回了一个普通函数,接着再执行这个函数,此时就相当于在全局作用域中执行了一个普通函数,所以它的this指向window,打印出南玖person1.foo3.call(person2)()这个与上面类似,也是返回了一个普通函数再执行,其实前面的执行都不用关心,它也是相当于在全局作用域中执行了一个普通函数,所以它的this指向window,打印出南玖person1.foo3().call(person2)这里就是把foo3返回的普通函数的this绑定到person2上,所以打印出jiuperson1.foo4()(),先执行person1.foo4()返回了一个箭头函数,再执行这个箭头函数,由于箭头函数的this始终指向它的上层作用域,所以打印出nanperson1.foo4.call(person2)(),与上面类似只不过使用call把上层作用域的this改成了person2,所以打印出jiuperson1.foo4().call(person2),这里是先执行了person1.foo4(),返回了箭头函数,再试图通过call改变改变该箭头函数的this指向,上面我们说到箭头函数的this始终指向它的上层作用域,所以打印出nan原文首发地址点这里,欢迎大家关注公众号 「前端南玖」
我是南玖,我们下期见!!!
-------------------------------------------
个性签名:智者创造机会,强者把握机会,弱者坐等机会。做一个灵魂有趣的人!
如果这篇文章有帮助到你,??关注+点赞??鼓励一下作者,文章公众号首发,关注 前端南玖 第一时间获取最新的文章~
欢迎加入前端技术交流群:928029210(QQ)
扫描下方二维码关注公众号,回复进群,拉你进前端学习交流群(WX),这里有一群志同道合的前端小伙伴,交流技术、生活、内推、面经、摸鱼,这里都有哈,快来加入我们吧~ 回复资料,获取前端大量精选前端电子书及学习视频~
