Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

再聊箭头函数 Arrow Function #9

Open
ryancui92 opened this issue Aug 30, 2017 · 0 comments
Open

再聊箭头函数 Arrow Function #9

ryancui92 opened this issue Aug 30, 2017 · 0 comments

Comments

@ryancui92
Copy link
Owner

昨天面试的时候问了一个关于箭头函数的问题,回来发现好像自己说错了(装逼失败),于是便重新认识一下这个 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 中进行的,实际上就是包含箭头函数定义的函数执行环境中定义的,这与我们上文的结论一致。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant