箭头函数和普通函数的区别

100 阅读4分钟

前言

ES6 中带来了更简洁写法的箭头函数,小伙伴们可不要为了图简洁而掉入坑中。本文详细带大家理解箭头函数。

官方定义

箭头函数表达式的语法比函数表达式更简洁,并且没有自己的thisargumentssupernew.target。箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数。

引入箭头函数有两个方面的作用:更简短的函数并且不绑定this。后文将按照这两方面来理解箭头函数。

箭头函数和普通函数的this

在箭头函数出现之前,每一个新函数根据它是被如何调用的来定义这个函数的 this 值:

  • 如果该函数是一个构造函数,this 指针指向一个新的对象
  • 在严格模式下的函数调用下,this 指向undefined;非严格模式,this 指向 windowglobal
  • 如果该函数是一个对象的方法,则它的 this 指针指向这个对象
  • 等等

示例:

function Person() {
  // Person() 构造函数定义 `this`作为它自己的实例。
  this.age = 0;

  setInterval(function growUp() {
    // 在非严格模式,growUp() 函数定义 `this`作为全局对象,
    // 与在 Person() 构造函数中定义的 `this`并不相同。
    this.age++;
    console.log(this.age) // NaN NaN ...
  }, 1000);
}

var p = new Person();

而箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承 this

function Person() {
  // Person() 构造函数定义 `this`作为它自己的实例。
  this.age = 0;

  setInterval(() =>  {
    this.age++; // |this| 正确地指向 p 实例
    console.log(this.age) // 1 2 3 ...
  }, 1000);
}

var p = new Person();

有人说箭头函数的this是在定义的时候就确定好了的,这样理解也没错。因为函数的作用域是创建或者说定义的时候就确定好了,而this继承自己作用域链的上一层。

Tip

箭头函数的 this 只在作用域链的上一层找,而不会沿着作用域链一直找。

示例:

function Person() {
  // Person() 构造函数定义 `this`作为它自己的实例。
  this.age = 0;

  function Child() {
    this.name = '123'

    setInterval(() => {
      // 在非严格模式,growUp() 函数定义 `this`作为全局对象,
      // 与在 Person() 构造函数中定义的 `this`并不相同。
      this.age++;
      console.log(this.age) // NaN ...
      console.log(this.name) // 123 ...
    }, 1000);
  }
  Child()
}

var p = new Person();

箭头函数的 this 继承自Child,这个this不会像变量一样会沿着作用域一直找。

Tip

通过 call 、 apply 、 bind 调用时,只能传递参数(不能绑定 this),他们的第一个参数会被忽略。

var adder = {
  base : 1,

  add : function(a) {
    var f = v => v + this.base;
    return f(a);
  },

  addThruCall: function(a) {
    var f = v => v + this.base;
    var b = {
      base : 2
    };

    return f.call(b, a); // 第一个参数会被忽略
  }
};

console.log(adder.add(1));         // 输出 2
console.log(adder.addThruCall(1)); // 仍然输出 2

Tip

如何抉择,什么时候使用普通函数,什么时候使用箭头函数?我们看一个 Object.defineProperty 的例子。

'use strict';
var obj = {
  a: 10
};

Object.defineProperty(obj, "b", {
  get: () => {
    console.log(this.a, typeof this.a, this);
    return this.a + 10;
   // 代表全局对象 'Window', 因此 'this.a' 返回 'undefined'
  }
});

obj.b; // undefined   "undefined"   Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

上述箭头函数换成普通函数,this就会指向obj。其实,使用何种函数取决于你对函数内 this 的需求。如果你需要this指向调用的对象,那就用普通函数。其他场景基本可以用箭头函数取代普通函数,毕竟箭头函数是很简洁的。

更简短的函数

省略 return

var func = x => x * x;
// 简写函数 省略 return

返回对象字面量

var func = () => ({foo: 1});

闭包

var Add = (i = 0) => () => (++i);

返回了一个函数 () => (++i),该函数包含了对外部变量的引用。

箭头函数的限制

没有arguments

示例:

const add = (x) => console.log(arguments[0]) // Uncaught ReferenceError: arguments is not defined

add(1)

隐式绑定arguments:

function foo(n) {
  // 隐式绑定 foo 函数的 arguments 对象。arguments[0] 是 n,即传给 foo 函数的第一个参数
  var f = () => arguments[0] + n; 
  return f();
}

使用剩余参数:

function foo(arg1, arg2) {
  var f = (...args) => args[1];
  return f(arg1, arg2);
}
foo(1, 2); //2

不能作为构造器

和 new一起用会抛出错误。

var Foo = () => {};
var foo = new Foo(); // TypeError: Foo is not a constructor

没有 prototype 属性

var Foo = () => {};
console.log(Foo.prototype); // undefined

不能用作函数生成器

yield关键字通常不能在箭头函数中使用(除非是嵌套在允许使用的函数内)。