一、前言
我写的基本所有文章都是面试题引发的,这篇也不例外哈哈
关于ES6中的箭头函数相信很多人在项目中都用过,但是确实没有深入研究过和普通函数有什么区别,被面试毒打过的我慢慢养成了喜欢深究的习惯,然而不在像在学校一样,“凡事多问几个为什么”,现在变成了,“凡是多查为什么”
对JavaScript的了解越深入,越发现这门语言的魅力
二、 箭头函数
箭头函数(Arrow Function),最早接触这个还是在大四实习的时候,那时候学了一点vue语法边开始做公司vue的项目,项目用的 vue-element-admin
这套继承方案,才了解到ES6新特性,箭头函数 ()=>{}
、对象展开运算符 ...
然而因为项目进度催得紧,就没有仔细看,那时候对箭头函数的理解仅仅停留在方便定义函数。
当时埋下的雷,在面试时都踩了,泪奔。。
本文主要围绕箭头函数几个特点展开,分别是 书写格式
, this问题
, 不可作为构造函数
, yield
, arguments对象
1. 书写形式
首先是我最粗浅的理解,也就是为了书写方便
首先箭头函数就是函数的缩写,如
var f = function(num) {
return num
}
这是普通函数写法,换成ES6中箭头函数的写法就是
三种写法是等效的
var f = (num) => { return num }
// 参数只有一个,因此可以省略括号
var f = num => { return num }
// 如果语句只包含一个return语句,可以省略大括号
var f = num => num;
2. this指向问题(最核心)
首先上结论!!!重点理解这句,理解了这句,箭头函数也就掌握了70%
箭头函数是个寄生虫,他不会创建自己的 this,它只会从自己的
作用域链
上找父级执行上下文
的 this箭头函数的this是创建时确定的,普通函数的this是执行时确定的
引用阮一峰在箭头函数的文章中的一个例子
下面是 Babel 转箭头函数产生的 ES5 代码,就能清楚地说明this
的指向。
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
经过Babel编译后,变为这样
function foo() {
var _this = this;
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}
能充分说明箭头函数this指向,这里还能说明一个问题,如果foo函数作用域中的this发生了改变,箭头函数的this也会发生改变,看下面这个例子
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
let obj = {
id: 42
}
foo.call(obj);
这个例子中通过call方法显式的改变了foo的this,箭头函数的this也就会随之发生改变,也指向obj对象, 再看一个我觉得没啥意义的例子,不过刚开始没仔细看,被绕进去了
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
首先通过call改变foo的this指向,使之指向一个匿名对象{id:1},然后将foo函数返回的箭头函数赋值给一个变量f,在通过call改变这个函数变量f的this
写的有点绕,我觉得可以从两个方面理解这个问题
第一种:其实f的本质是箭头函数,f保存的是箭头函数的引用,箭头函数不能通过call和apply改变this作用域,所以失败
第二种:箭头函数的作用域其实是指向foo函数执行上下文的this,只有改变foo函数this,箭头函数的this才会改变,而最后三行都在改变这个箭头函数的this,显然达不到目的,this也就没发生改变
看一个反例:
const cat = {
lives: 9,
jumps: () => {
this.lives--;
}
}
cat.jumps();
cat.jumps();
cat.jumps();
console.log(cat.lives); // 9
解释一下为什么?
3. 不可当作构造函数
其实这个和第一个点一样,都是和this有关,因为在通过new构造一个对象的时候,要改变this作用域,将this指向新创建的对象,然而构造函数没有自己的this,因此不可以,而且这么写代码会报错
var Person = () => {
this.name = "111";
}
var person = new Person(); // Uncaught TypeError: Person is not a constructor
还有一个需要注意的点,有人也把这个当成箭头函数和普通函数的区别,不过我认为和这个其实说的是一个意思,因为不能作为构造函数,也就不具有prototype,因为原型链的绑定就是通过new关键字(没深究过,觉得这句话应该没问题)
除了不能作为构造函数,在箭头函数中还不能使用super以及new.target
new.target是用来检测构造函数是否通过new关键字调用的,例:
function fun() {
if (new.target) {
console.log("是通过new构造的")
} else {
throw new Error("fun必须通过new调用")
}
}
var e = new fun(); // 是通过new构造的
var f = fun(); // Uncaught Error: fun必须通过new调用
super在class部分用的比较多,和java中的super关键字差不多,都是调用父类的方法,直接通过super()代表调用父类构造方法(java继承思想),super.test() 代表调用父类的test方法
4. 没有arguments
阮一峰在ES6系列文章中曾说不可以使用arguments对象,有些人认为使用会报错,其实不是,在箭头函数中可以使用arguments对象,只是arguments对象也指向外层的arguments
作为替代,可以使用rest参数,如下例:
function fun() {
return (...rest) => {
console.log(rest);
}
}
fun()(1, 2, 3, 4);
上述箭头函数中通过…rest接受了外界传递的参数,和arguments效果差不多,
5. yield问题
箭头函数中不能使用yield关键字,因此不能作为Generate函数
后续在补充
三、总结
虽然总结了五条不同点,但是最核心的应该还是this的问题,通过this问题可以抓出构造函数以及arguments的问题,yield目前用的还不太多,大概做个了解,后边在补充,在就是书写形式上比普通函数简化了很多