箭头函数

886 阅读5分钟

在es6中新增了一种函数的定义方式——箭头函数(=>),箭头函数较传统函数而言,其写法更简洁。

// ES6语法
const fn = v => v;

// 传统函数
const fn = function(v) {
    return v;
}

箭头函数的特点

1)语法简洁

箭头函数省去了`function`关键字,用`=>`代替function,圆括号代表参数部分,当只有一个参数时,圆括号可省略,当只有一行返回语句时,`return`和花括号`{}`都可以省略。

```js
// 求两数之和
const fn = (a, b) => {return a + b;}; // 等价于 const fn = (a, b) => a + b;

// 求一个数组各项之和
const sum = [1, 2, 3, 4, 5].reduce((x, y) => x + y, 0); // 15

// 将数组中的元素按从小到大顺序排序
const array = [2, 4, 1, 5, 9, 7].sort((x, y) => x - y); //  [1, 2, 4, 5, 7, 9]

// 过滤数组中为偶数的数字
const array = [0, 1, 2, 3, 4, 5, 6].filter(x => x % 2 === 0); // [0, 2, 4, 6]
```

(2)不绑定 this

箭头函数体内的`this`永远指向的是定义时所在的对象,而不是调用时所在的对象。
```js
function Fn() {
  this.s1 = 0;
  this.s2 = 0;

  // 箭头函数
  setInterval(() => this.s1++, 1000);

  // 普通函数
  setInterval(function() {
    this.s2++;
  }, 1000);
}

var fn = new Fn();
setTimeout(() => console.log('s1= ', fn.s1), 3100); // 3.1秒后输出s1= 3
setTimeout(() => console.log('s2= ', fn.s2), 3100); // 3.1秒后输出s2= 0
```
上面代码中,Fn函数内部设置了两个定时器,分别使用了箭头函数和普通函数。箭头函数中的`this`指向定义时所在的作用域(即Fn函数),this.s1++是处在箭头函数中,这里的this就是`fn`,所以fn.s1的值为3。

而普通函数中的`this`指向运行时所在的作用域(即全局对象window),this.s2++实际等于window.s2++,fn.s2一次都没有更新,因而得到的是0。

所以,从严格意义上讲,箭头函数中不会创建自己的`this`,而是会从自己作用域链的上一层继承。

```js
const Person = {
  name: 'Kimmy',
  age: 20,
  doSomething: function() {
    setTimeout(() => console.log(`name: ${this.name}, age: ${this.age}`), 1000);
  }
};

Person.doSomething(); // name: Kimmy, age: 20

const Person2 = {
  name: 'Kimmy',
  age: 20,
  doSomething: () => {
    setTimeout(() => console.log(`name: ${this.name}, age: ${this.age}`), 1000);
  }
};

Person2.doSomething(); // name: , age: undefined
```
上面两段代码的唯一区别在于doSomething()函数的写法,Person中使用了普通函数定义,Person2中使用了箭头函数定义。

在第一段代码中,Person.doSomeThing()中的`this`指向函数的调用体,即`Person`本身,在调用`setTimeout()`函数时,由于其函数体部分是通过箭头函数定义的,内部的`this`会继承父作用域的`this`(即`Person`),从而输出`name: Kimmy, age: 20`。

在第二段代码中,Person2.doSomething()中的`this`指向外层作用域,而Person2的父作用域是全局作用域window,在调用`setTimeout()`函数时,由于其函数体部分是通过箭头函数定义的,内部的`this`会继承`doSomething()`函数所在的作用域`this`(即window),而window上name属性是`''`,age属性不存在,所以`Person2.doSomething()`输出`name: , age: undefined`。

综上所述,箭头函数根本没有自己的`this`,导致内部的`this`就是外层代码块的`this`。正因为它没有`this`,所以也就`不能用作构造函数`。

(3)不支持 call()、 apply()、bind() 函数的特性

我们都知道通过调用 `call()` 、`apply()`、`bind()` 函数可以改变一个函数的执行主体,即改变被调用函数中 `this` 的指向,但箭头函数中不支持 `call()` 、`apply()`、`bind()`等,因为箭头函数中没有自己的 `this` ,而是继承父作用域中的 `this`。

```js
function fn() {
  return () => {
    console.log(`id= ${this.id}`);
  }
}

let f = fn.call({id: 1}); // id= 1
let f2 = f.call({id: 2})()(); // id = 1
let f3 = f().call({id: 3})(); // id= 1
let f4 = f()().call({id: 4}); // id= 1
```
上面代码中只有一个`this`,即函数`fn`的`this`,所以`f2`、`f3`、`f4`都输出同样的结果。因为内层函数时箭头函数,没有自己的`this`,它们的`this`都是最外层`fn`函数是`this`。

(4)不绑定 arguments

在普通函数`function`中,我们可以通过`arguments`对象来获取到实际传入的参数值,但在箭头函数中是不存在的。
```js
const fn = () => {
  console.log(arguments);
}
fn(1, 2); // Uncaught ReferenceError: arguments is not defined

function fn() {
  setTimeout(() => {
    console.log(arguments);
  }, 0);
}

fn(1, 2); // [1, 2]
```
通过上面第一段代码可以看出,在浏览器环境下,箭头函数中使用`arguments`时,会抛出异常,第二段代码之所以能正常运行,原因在于第二段代码中的`arguments`其实是函数`fn`中的变量。

因此,箭头函数中无法使用`arguments`,同样也就无法使用`caller`、`callee`。

虽然我们无法通过`arguments`来获取实参,但是我们可以借助rest运算符(`...`)来达到这个目的。
```js
const fn = (...args) => {
  console.log(args);
}

fn(1, 2); // [1, 2]
```

(5)支持嵌套
```js
const pipeline = (...funcs) => val => funcs.reduce((a, b => b(a)), val);
const plus1 = a => a + 1;
const mult2 = a => a * 2;
const addThenMult = pipeline(plus1, mult2);
addThenMult(5); // 12
```

箭头函数不适用的场景

1)定义对象的方法且该方法内部包括`this`
```js
const Person = {
  name: 'Kimmy',
  age: 20,
  doSomething: () => {
    this.age++;
  }
};
```
Person.doSomething()方法是一个箭头函数,调用Person.doSomething()时,如果是普通函数,该方法内部的`this`指向Person,如果写成上面那样的箭头函数,使得`this`指向全局对象。

(2)不能作为构造函数,不能使用 new 操作符
构造函数时通过`new`操作符生成对象实例的,生成实例的过程也是通过构造函数给实例绑定`this`的过程,而箭头函数没有自己的`this`,因此不能使用箭头函数作为构造函数,也不能通过`new`操作符来调用箭头函数。

```js
// 普通函数
function Person(name) {
  this.name = name;
}
let p = new Person('Kimmy'); // 正常

// 箭头函数
let Person = (name) => {
  this.name = name;
}
let p = new Person('Kimmy'); // Uncaught TypeError: Person is not a constructor
```

(3)没有 prototype 属性
```js
let a = () => 1;

function b() {
  return 2;
}

a.prototype // undefined
b.prototype // {constructor: ƒ}
```

(4)不适合将原型函数定义成箭头函数
在给构造函数添加原型函数时,如果使用箭头函数,其中的`this`会指向全局作用域`window`,而并不会指向构造函数,因此并不会访问到构造函数本身,也就无法访问到实例属性,这就失去了作为原型函数的意义。

参考文档:

函数的扩展