ECMAScript6 之箭头函数™

1. 什么是箭头函数

1.1. 箭头函数简介

箭头函数的语法非常简单 (parameters) => { statements },箭头函数只能用赋值式写法,不能用声明式写法。看一下最简单的箭头函数表示法:

1
2
let sayHello = () => { console.log('Hello World'); }
sayHello(); // Hello World

1.2. 箭头函数的特点

和普通函数相比,箭头函数主要就是以下三个方面的特点:

  • 更简化的代码语法
  • 没有局部 this 的绑定
  • 不绑定 arguments

2. 更简化的代码语法

2.1. 一般写法

(parameters) => { statements }

1
2
3
4
5
6
7
8
9
10
11
12
// 求两个数的和并在控制台输出
// ES6
let sum = (a, b) => {
console.log(a + b);
return a + b;
}

// ES5
var sum = function(a, b) {
console.log(a + b);
return a + b;
};

2.2. 省略 () 的情况

如果只有一个参数,可以省略()括号。
parameters => { statements }

1
2
3
4
5
6
7
8
9
10
11
12
// 求一个数的两倍,并输出
// ES6
let double = num => {
console.log(num * 2);
return num * 2;
}

// ES5
var double = function(num) {
console.log(num * 2);
return num * 2;
};

2.3. 省略 {} 的情况

如果返回值仅仅只有一个表达式(expression), 还可以省略大括号{}
parameters => statements

1
2
3
4
5
6
7
8
// 求一个数的两倍
// ES6
let double = num => num * 2;

// ES5
var double = function(num) {
return num * 2;
};

2.4. 特殊情况

如果箭头函数直接返回一个对象,必须在对象外面加上括号。

1
2
3
4
5
6
7
8
9
10
// ES6
let returnObj = () => ({name: "xiguapi", age: 23});

// ES5
var returnObj = function() {
return {
name: "xiguapi",
age: 23
}
};

3. 没有局部 this 的绑定

和一般的函数不同,箭头函数不会绑定 this。 或则说箭头函数不会改变 this 本来的绑定。
我们用一个例子来说明:

1
2
3
4
function Counter() {
this.num = 0;
}
var a = new Counter(); // a.num 0

因为使用了关键字 new 构造,Count() 函数中的 this绑定到一个新的对象,并且赋值给 a。通过 console.log 打印 a.num,会输出 0。

如果我们想每过一秒将 a.num 的值加 1,该如何实现呢?可以使用 setInterval() 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
function Counter() {
this.num = 0;
this.timer = setInterval(function add() {
this.num++;
console.log(this.num);
}, 1000);
}

var b = new Counter();
// NaN
// NaN
// NaN
// ...

首先函数 setInterval 没有被某个声明的对象调用,也没有使用 new 关键字,再之没有使用 bind, callapplysetInterval 只是一个普通的函数。实际上 setInterval 里面的 this 绑定到全局对象 window 上了。

使用箭头函数!使用箭头函数就不会导致 this 被绑定到全局对象。

1
2
3
4
5
6
7
8
9
10
11
12
function Counter() {
this.num = 0;
this.timer = setInterval(() => {
this.num++;
console.log(this.num);
}, 1000);
}
var b = new Counter();
// 1
// 2
// 3
// ...

通过 Counter 构造函数绑定的 this 将会被保留。在 setInterval 函数中,this 依然指向我们新创建的 b 对象。

3. 不绑定 arguments

箭头函数还有一个比较有特点的地方就是其不绑定 arguments,即如果你在箭头函数中使用 arguments 参数不能得到想要的内容。

1
2
3
let arrowfunc = () => console.log(arguments.length)
arrowfunc()
// arguments is not defined

4. 注意点

  • 箭头函数适合于无复杂逻辑或者无副作用的纯函数场景下,例如:用在 map、reduce、filter 的回调函数定义中
  • 箭头函数的亮点是简洁,但在有多层函数嵌套的情况下,箭头函数反而影响了函数的作用范围的识别度,这种情况不建议使用箭头函数
  • 箭头函数要实现类似纯函数的效果,必须剔除外部状态。所以箭头函数不具备普通函数里常见的 this、arguments 等,当然也就不能用 call()、apply()、bind() 去改变 this 的指向
  • 箭头函数不适合定义对象的方法(对象字面量方法、对象原型方法、构造器方法),因为箭头函数没有自己的 this,其内部的 this 指向的是外层作用域的 this
  • 箭头函数不适合定义结合动态上下文的回调函数(事件绑定函数),因为箭头函数在声明的时候会绑定静态上下文
1
2
const json = {bar: 1, fn: () => console.log(this.bar)};
json.fn(); //-> undefined
1
2
3
4
5
6
7
function Foo() {
this.bar = 1;
}

Foo.prototype.fn = () => console.log(this.foo);
const foo = new Foo();
foo.fn(); //-> undefined // this 并不是指向 Foo,根据变量查找规则,回溯到了全局作用域
1
2
3
4
5
const Message = (text) => {
this.text = text;
};
var helloMessage = new Message('Hello World!');
console.log(helloMessage.text); //-> Message is not a constructor // 不可以当作构造函数,也就是说,不可以使用 new 命令
1
2
3
4
const button = document.querySelector('button');
button.addEventListener('click', () => {
this.textContent = 'Loading...';
}); // this 并不是指向预期的 button 元素,而是 window