箭头函数是匿名函数,ES5匿名函数的语法糖;但又增加了ES5所没有的一些优点,接下来我们一起来看一看箭头函数
// ES5
vat tt = function tt() {
return 55 + 99;
}
// ES6
vat tt = () => 55 + 99
// 是不是一对比,写法的差异就看出来了
ES6增加了箭头函数:
let func = value => value;
// 相当于
let func = function () {
return value;
}
// 如果需要给函数传入多个参数:
let func = (value, num) => value * num;
// 如果函数的代码块需要多条语句:
let func = (value, num) => {
return value * num;
}
// 如果需要直接返回一个对象:
let func = (value, num) => ({total: value * num});
// 与变量解构结合
let func = ({value, num}) => ({total: value * num})
// 使用
var result = func({
value: 10,
num: 10
})
console.log(result); // {total: 100}
以上是箭头函数的用法,了解了箭头函数的用法之后呢,我们一起来进一步的看一看箭头函数,到底和我们的普通函数有什么区别,好处在哪?
1.没有this
箭头函数没有this,所以需要通过查找作用域链来确定this的值。
这就意味着如果箭头函数被非箭头函数包含,this绑定的就是最近一层非箭头函数的this。
模拟一个实际开发中的例子:
我们的需求是点击一个按钮,改变该按钮的背景色。
为了方便开发,我们抽离一个Button组件,当需要使用的时候直接:传入元素id值即可绑定该元素点击时改变背景色的事件
new Button('button');
// HTML代码如下:
点击变色
// javascript代码:
function Button(id) {
this.element = document.querySelector('#' + id);
this.bindEvent();
}
Button.prototype.bindEvent = function() {
this.element.addEventListener("click", this.setBgColor, false);
}
Button.prototype.setBgColor = function() {
this.element.style.backgroundColor = '#abcdef';
}
vat button = new Button('button');
看着好像没有问题,结果却是报错 Uncaught TypeError: Cannot read property 'style' of undefined
这是因为当使用addEventListener() 为一个元素注册事件的时候,事件函数里的this值是该元素的引用。
所以如果我们在setBgColor中console.log(this), this指向的是按钮元素,那this.element就是undefined, 报错自然就理所当然了。
也许你会问,既然this都指向了按钮元素,那我们直接修改setBgColor函数为:
Button.prototype.setBgColor = function() {
this.style.backgroundColor = '#abcdef';
}
不就可以解决这个问题了?
确实可以这样做,但是在实际开发中,我们可能会在setBgColor中还调用其他的函数,比如写成这样:
Button.prototype.setBgColor = function() {
this.setElementColor();
this.setOtherElementColor();
}
所以我们还是希望setBgColor中的this是指向实例对象的,这样就可以调用其他的函数。
利用ES5,我们一般会这样做:
Button.prototype.bindEvent = function() {
this.element.addEventListener("click", this.setBgColor.bind(this), false);
}
为避免addEventListener的影响,使用bind强制绑定setBgColor()的this为实例对象
使用ES6,我们可以更好的解决这个问题:
Button.prototype.bindEvent = function() {
this.element.addEventListener("click", event => this.setBgColor(event), false);
}
由于箭头函数没有this,所以会向外层查找this的值,即bindEvent中的this,此时this指向实例对象,所以可以正确的调用this.setBgColor方法,而this.setBgColor中的this也会正确指向实例对象。
在这里再额外提一点,就是注意bindEvent和setBgColor在这里使用的是普通函数的形式,而非箭头函数,如果我们改成箭头函数,会导致函数里的this指向window对象(非严格模式下)。
最后,因为箭头函数没有this,所以也不能用call()、apply()、bind()这些方法改变this的指向,可以看一个例子:
var value = 1;
var result = (() => this.value).bind({value: 2})();
console.log(result); // 1
2.没有arguments
箭头函数没有自己的arguments对象,这不一定是件坏事,因为箭头函数可以访问外围函数的arguments对象:
function constant() {
return () => arguments[0];
}
var result = constant(1);
console.log(result()); // 1
那如果我们就是要访问箭头函数的参数呢?
你可以通过命名参数或者rest参数的形式访问参数:
let nums = (...nums) => nums;
3.不能通过new关键字调用
Javascript函数有两个内部方法:[[Call]]和[[Construct]]。
当通过new调用函数时,执行[[Construct]]方法,创建一个实例对象,然后再执行函数体,将this绑定到实例上。
当直接调用的时候,执行[[Call]]方法,直接执行函数体。
箭头函数并没有[[Construct]]方法,不能被用作构造函数,如果通过new的方式调用,会报错。
var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor
4.没有原型
由于不能使用new调用箭头函数,所以也没有构建原型的需求,于是箭头函数也不存在prototype这个属性。
5.没有super
连原型都没有,自然也不能通过super来访问原型的属性,所以箭头函数也没有super的,不过跟this、arguments一样,这些值由外围最近一层非箭头函数决定。
在出现了箭头函数之后是不是就可以舍弃原来的普通函数了呢?答案一定是否定的,一些情况下我们最好不要去进行箭头函数的操作,那都有哪些情况呢?
(1) 在对象上定义函数
先来看下面这段代码
var obj = {
array: [1, 2, 3],
sum: () => {
console.log(this === window); // => true
return this.array.reduce((result, item) => result + item);
}
}
obj.sum();
// Throws "TypeError: Cannot read property 'reduce' of undefined"
sum方法定义在obj对象上,当调用的时候我们发现抛出了一个TypeError,因为函数中的this是window对象,所以this.array也是undefined。原因也很简单,相信只要了解es6 箭头函数的都知道
箭头函数没有它自己的this值,箭头函数内的this值继承自外围作用域
解决方法也很简单,就是不用呗。这里可以用es6里函数表达式的简洁语法,在这种情况下,this值就取决于函数的调用方式了。
var obj = {
array: [1, 2, 3],
sum() {
console.log(this === obj); // => true
return this.array.reduce((result, item) => result + item);
}
}
obj.sum(); // => 6
通过object.method()语法调用的方法使用非箭头函数定义,这些函数需要从调用者的作用域中获取一个有意思的this值。
(2)在原型上定义函数
在对象原型上定义函数也是遵循着一样的规则
function Person(pName) {
this.pName = pName;
}
Person.prototype.sayName = () => {
console.log(this === window); // => true
return this.pName;
}
var person = new Person('zhangSan');
person.sayName(); // => undefined
// 使用function函数表达式
function Person(pName) {
this.pName = pName;
}
Person.prototype.sayName = function() {
console.log(this === person); // => true
return this.pName;
}
var person = new Person('zhangSan');
person.sayName(); // => zhangSan
(3)动态上下文中的回调函数
this是js中非常强大的特点,他让函数可以根据其调用方式动态的改变上下文,然后箭头函数直接在声明时就绑定了this对象,所以不再是动态的。
在客户端,在dom元素上绑定事件监听函数是非常普遍的行为,在dom事件被触发时,回调函数中的this指向该dom,可当我们使用箭头函数时:
var button = document.getElementById('myButton');
button.addEventListener('click', () => {
console.log(this === window); // => true
this.innerHTML = 'clicked button';
})
因为这个回调的箭头函数是在全局上下文中被定义的,所以他的this是window。所以当this是由目标对象决定时,我们应该使用函数表达式:
var button = document.getElementById('myButton');
button.addEventListener('click', function() {
console.log(this === button); // => true
this.innerHTML = 'clicked button';
})
(4)构造函数中
在构造函数中,this指向新创建的对象实例
this instanceOf MyFunction === true
需要注意的是,构造函数不能使用箭头函数,如果这样做会抛出异常
var Person = (name) => {
this.name = name;
}
var person = new Person('zhangSan');
// Uncaught TypeError: Person is not a constructor
理论上来说也是不能这么做的,因为箭头函数在创建时this对象就绑定了,更不会指向对象实例。
(5)太简短的函数
箭头函数可以让语句写的非常的简洁,但是一个真实的项目,一般由多个开发者共同协作完成,就算由单人完成,后期也并不一定是同一个人维护,箭头函数有时候并不会让人很好的理解,比如
let multiply = (a, b) => b === undefined ? b => a * b : a * b;
let double = multiply(2);
double(3); // => 6
multiply(2,3); // => 6
这个函数的作用就是当只有一个参数a时,返回接受一个参数b返回a * b的函数,接受两个参数时直接返回乘积,这个函数可以很好的工作并且看起来很简洁,但是从第一眼看去并不是很好理解。为了让这个函数更好的让人理解,我们可以为这个箭头函数加一对花括号,并加上return语句买或者直接使用函数表达式:
function multiply(a, b) {
if (b === undefined) {
return function (b) {
return a * b;
}
}
return a * b;
}
let double = multiplu(2);
double(3); // => 6;
multiply(2, 3); // => 6
总结
最后,关于箭头函数,引用MDN的介绍就是:箭头函数表达式的语法比函数表达式更短,并且不绑定自己的this,arguments,super。
毫无疑问,箭头函数带来了很多便利。恰当的使用箭头函数可以让我们避免使用早起的.bind()函数或者需要固定上下文的地方并且让代码更加简洁。
箭头函数也有一些不便利的地方。我们在需要动态上下文的地方不能使用箭头函数:定义需要动态上下文的函数,构造函数,需要this对象作为目标的回调函数以及用箭头函数难以理解的语句。在其他情况下,请尽情使用箭头函数。