blog icon indicating copy to clipboard operation
blog copied to clipboard

再聊箭头函数 Arrow Function

Open ryancui92 opened this issue 7 years ago • 0 comments

昨天面试的时候问了一个关于箭头函数的问题,回来发现好像自己说错了(装逼失败),于是便重新认识一下这个 ES6 用得最多的东西。

这段时间面试了很多人,当问到箭头函数带来的好处时,大部分人都只说到了写得少(???) ,不需要用 thatself 之类的变量,可以直接使用 this。但当细问下去,其实很多人不了解箭头函数的 this 就是是啥回事(或者包括我自己

先说说在此之前我对箭头函数的理解吧:箭头函数里的 this 是「继承」的,本身不存在自己的 this,当调用到 this 时用的其实是上一层作用域的 this。现在看来这个理解还是有一定偏差。

闭包与词法作用域

摘录一段犀牛书的代码,就能很好说明闭包和词法作用域的问题了:

var scope = 'global scope';
function checkscope() {
  var scope = 'local scope';
  function f() { return scope; }
  return f;
}
checkscope()();

这里返回的是 'local scope',是因为在函数 f 函数体内访问 scope 变量时,会沿着从 f 开始的作用域链往上查找,因此第一个查找到的变量是在 checkscope 作用域内定义的 scope

注意无论 f 在何时何地调用,返回的 scope 总是 'local scope',这是由于作用域查找总是从 f 开始,因此 scope 的值通过词法作用域确定了。

Function 的 this

在 ES5 中,函数中的 this 是动态变化的,会根据调用方式的不同产生不同的绑定,只有在被调用时才能确定 this 的值:

  • 普通函数调用时,全局对象(浏览器下非严格模式 window
  • 作为对象方法调用时,为该对象
  • 作为构造函数调用时,为新对象的引用
  • 使用 callapply 调用,为绑定的值(第一个参数)

用代码说明一下:

function f(change) {
  if (change) {
    this.a = 300;
  }
  console.log(this.a);
}

var a = 100;
var obj = {
  a: 200
};

// 普通调用,输出 100
f();

// 方法调用,输出 200
obj.f = f;
obj.f();

// 构造函数调用,改变的是新对象的 a,输出 300, 100, 300
var newObj = new f(true);
console.log(a);
console.log(newObj.a);

Arrow Function 的 this

先看 MDN 的说明:MDN - 箭头函数,其实写得很清楚了。关键在于箭头函数的 this 是词法作用域绑定这个概念。这意味着箭头函数的 this 类似于一个变量,在定义的时候就已经确定了指向的上下文,而非动态获取。看个例子:

var a = 100;
var obj1 = {
  a: 200,
  b: () => {
    console.log(this.a);
  }
};
obj1.b();

var obj2 = {
  a: 200,
  b: function () {
    var f = () => {
      console.log(this.a);
    }
    f();
  }
};
obj2.b();
var ff = obj2.b;
ff();

在箭头函数中,this 可以看成是一个变量,而非关键字,箭头函数本身作用域里并没有 this 的定义,当引用 this 时,会沿着作用域链往上查找(跟闭包那个 scope 好像哦)。

因此例子中的 obj1.b()obj2.b() 输出分别为 100 和 200,由于 obj1.b 在查找 this 时会一直查找到全局作用域,因此 this.a = 100;而 obj2.bfunction 作用域就找到了 this(注意使用了 function 定义了函数),而这时的 this 为方法调用的对象 obj2,因此 this.a = 200

进一步说明这种作用域查找规则的是下面两行,输出是 100. 由于此时 obj2.b 的调用是一个普通函数调用,因此 this 的值是全局对象,所以输出的是 window.a 了。

Arrow Function 到底是啥

在了解到箭头函数内部的 this 其实是一个词法绑定的变量时,我不禁怀疑这个箭头函数究竟是个啥,甚至于它是不是一个函数,有没有自己的作用域?

以前很多对箭头函数的简单解释是「JavaScript 函数的语法糖」,根据前文所述的 this 获取值的不同,已经知道箭头函数与普通函数其实有很大的不同,于是尝试一下:

var f = () => {};

// 'function'
typeof f;

// true
f instanceof Function;

// true
f.__proto__ === Function.prototype;

// 根据 MDN,箭头函数没有原型:undefined
f.prototype

各种证据表明这个箭头函数的确是一个「函数」,只是没有原型对象,因此也就无法作为构造函数调用(new f())。另外 MDN 提到,箭头函数内除了 this,还有其他普通函数中常用的变量如 arguments 也是词法绑定的,这些变量在箭头函数调用时是沿着作用域链向上查找的。

看看规范

14.2.16 Runtime Semantics: Evaluation

里面的 Note 提到箭头函数没有对 this 进行 local binding(本地绑定),这些变量的绑定是在 lexically enclosing environment 中进行的,实际上就是包含箭头函数定义的函数执行环境中定义的,这与我们上文的结论一致。

ryancui92 avatar Aug 30 '17 09:08 ryancui92