一、引言
相信大家对ES6的箭头函数都不陌生,我们也会在日常开发的过程中经常使用到它,但是箭头函数有哪些值得我们去注意的点呢?
二、箭头函数的几个使用注意点
1.函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
我们都知道,普通的函数里面的this是指向运行时的作用域的,但是箭头函数则不然,箭头函数中的this是绑定定义时所在的作用域的。
- 普通函数
function foo() {
setTimeout(function() {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 }); //id: 21
- 箭头函数
function foo() {
setTimeout(() => {
console.log('id:', this.id);
}, 100);
}
var id = 21;
foo.call({ id: 42 }); //id: 42
由上面两个例子可见,setTimeout里面的回调函数运行的时候的作用域是window,但是this定义的时候的作用域是函数foo。所以,如果是普通函数,执行时this应该指向全局对象window,这是应该输出21。但是,箭头函数导致this总是指向函数定义生效时所在的对象,所以输出42.
利用箭头函数的这个特点,我们可以使用箭头函数使this指向固定化,这种特性很有利于封装回调函数。下面是一个例子,DOM事件的回调函数封装在一个对象里面。
var handler = {
id: '123456',
init: function() {
document.addEventListener('click',
event => this.doSomething(event.type), false);
},
doSomething: function(type) {
console.log('Handling ' + type + ' for ' + this.id);
}
};
上面代码的init方法中,使用了箭头函数,这导致这个箭头函数里面的this,总是指向handler对象。否则,回调函数运行时,this.doSomething这一行会报错,因为此时this指向document对象。
很多小伙伴会感到很奇怪,为什么箭头函数的this不会因为函数运行时作用域的改变而改变?
其实实际的原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。
所以,箭头函数转成ES5的代码如下:
// ES6
function foo() {
setTimeout(() => {
console.log('id:', this.id); //其实这里的this就是使用了外层作用域的this
}, 100);
}
// ES5
function foo() {
var _this = this; //将外层作用域的this复制给一个变量
setTimeout(function () {
console.log('id:', _this.id);
}, 100);
}
2.不可以当作构造函数,也就是说,不能使用new命令,否则会抛出一个错误
上面第一点说到:箭头函数没有自己的this,所以理所当然箭头函数也不可以当作构造函数。
下面尝试使用箭头函数作为构造函数:
var fun = (id, name) => {
this.id = id;
this.name = name;
this.showName = () => {
console.log(this.name);
}
};
let obj = new fun(1, "hdl"); //TypeError: fun is not a constructor
obj.showName();
果然,报错了,说fun不是一个构造函数,这也反过来印证了第一点,箭头函数没有this。
3.不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用rest参数代替。
function func1(a, b) {
console.log(arguments);
}
let func2 = (a, b) => {
console.log(arguments); //arguments is not defined
}
func1(1, 2);
func2(1, 2);
如果非要打印函数参数,可以在箭头函数中使用rest参数代替arguments对象.
function func1(a, b) {
console.log(arguments);
}
let func2 = (...rest) => {
console.log(rest); //[1, 2]
}
func1(1, 2);
func2(1, 2);
4.没有new.target
new.target是ES6新引入的属性,普通函数如果通过new调用,new.target会返回该函数的引用。
function Cat() {
console.log(new.target);
}
let cat = new Cat(); // ƒ Cat() { console.log(new.target); }
此属性主要用于确定构造函数是否为new调用的。
在箭头函数里使用new.target会报错。
// 普通函数
let a = function() {
console.log(new.target);
}
a(); // undefined
// 箭头函数
let b = () => {
console.log(new.target); // 报错:Uncaught SyntaxError: new.target expression is not allowed here
};
b();
5.没有原型和super
由于不能通过 new 关键字调用,不能作为构造函数,所以箭头函数不存在 prototype 这个属性。
let func = () => {};
console.log(func.prototype) // undefined
箭头函数没有原型,故也不能通过 super 来访问原型的属性,所以箭头函数也是没有 super 的。同this、arguments、new.target 一样,这些值由外围最近一层非箭头函数决定。
6.call/apply/bind方法无法改变箭头函数中this的指向
这个也很容易理解,call()、apply()、bind()方法的共同特点是可以改变this的指向,用来动态修改函数执行时this的指向。但由于箭头函数的this定义时就已经确定了且不会改变。所以这三个方法永远也改变不了箭头函数this的指向。
var name = 'global name';
var obj = {
name: 'huangdonglu'
}
// 箭头函数定义在全局作用域
let func = () => {
console.log(this.name);
};
func(); // global name
// this的指向不会改变,永远指向Window对象,放到到window下的全局变量
func.call(obj); // global name
func.apply(obj); // global name
func.bind(obj)(); // global name
7.箭头函数的解析顺序相对考前
虽然箭头函数中的箭头不是运算符,但箭头函数具有与常规函数不同的特殊运算符优先级解析规则。
let a = false || function() {}; // ok
let b = false || () => {}; // SyntaxError: Malformed arrow function parameter list
let c = false || (() => {}); // ok
8.箭头函数不支持重名参数
function foo(a, a) {
console.log(a, arguments); // 2 Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ]
}
var boo = (a, a) => { // 直接报错:Uncaught SyntaxError: Duplicate parameter name not allowed in this context
console.log(a);
};
foo(1, 2);
boo(1, 2);
9.不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
三、箭头函数不适用场景
我们都知道,箭头函数比普通的函数要简洁很多,但是箭头函数也不是任何场景都可以适用的。
1.第一个场合是定义对象的方法,且该方法内部包括this。
- 对象里面的函数使用箭头函数
const cat = {
lives: 9,
jumps : () => {
this.lives--;
}
};
cat.jumps();
console.log(cat.lives); //9
- 对象里面的方法使用普通方法
const cat = {
lives: 9,
jumps() {
this.lives--;
}
};
cat.jumps();
console.log(cat.lives); //8
上面代码中,cat.jumps()方法是一个箭头函数,这是错误的。调用cat.jumps()时,如果是普通函数,该方法内部的this指向cat;如果写成上面那样的箭头函数,使得this指向全局对象,因此不会得到预期结果。这是因为对象不构成单独的作用域,导致jumps箭头函数定义时的作用域就是全局作用域。
2.需要动态this时,也不应该使用动态函数。
var btn = document.getElementById('btn');
btn.addEventListener('click', () => {
console.log(this);
});
因为btn的监听函数是一个箭头函数,导致里面的this就是全局对象,而不符合我们想操作按钮本身的需求。如果改成普通函数,this就会动态指向被点击的按钮对象。
除了上面这两点外,还总结了一下几点:
- 不应被用在定义对象的方法上
- 具有动态上下文的回调函数,也不应使用箭头函数
- 不能应用在构造函数中
- 避免在 prototype 上使用
- 避免在需要 arguments 上使用
四、总结
就先总结上面这么多了,感谢@阮一峰 阮老师的文章:箭头函数使用注意点,最近也在阅读NICHOLAS的《深入理解ES6》,希望能对ES6更加的熟悉,也希望大家能够对本文加以补充和指正。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。