对阮一峰《ES6 入门》中箭头函数 this 描述的质疑和探究

前言

昨日,发了一篇公众号文章:前端

原文连接:别低估本身,但,这道题,真的有点难git

在部分群里引发了一些讨论,其中有一点是关于箭头函数的 this 指针的问题。使用了阮一峰《ES6 入门》文章的内容来反驳。https://github.com/ruanyf/es6tutorial/blob/gh-pages/docs/function.md 为了隐私,屏蔽掉了微信昵称:es6

ryf_group

上述截图,来自阮一峰的《ECMAScript 6 入门》:github

this

下面咱们就来看看箭头函数的 this 究竟是啥样的!web

一道题引起的灾难

thisthis2

起初,群里一个朋友抛出了这个疑问,为啥这两个输出有差别。微信

一个是空 person,一个是有值的 person 呢?闭包

那么咱们首先就来分析一下究竟是什么缘由。app

普通函数的 getval

let pp = new person("251");编辑器

这里建立了一个 person 实例 pp函数

而后 pp.getval(),直接中了以前文章提到的 [谁调用,指向谁] 逻辑,pp 实例调用了 getval,因此 getval 的 this 指向 pp 实例,打印出 pp 实例内容。

这个看起来没有人有疑问,一切都很美好。

箭头函数的 getval

let pp = new person("251");

这里也建立了一个 pp 实例

在 pp.getval() 的时候,箭头函数的 this 指向谁呢?

参考文章 别低估本身,但,这道题,真的有点难

箭头函数:父级指向谁,当前箭头函数就指向谁。

这里 getval 的箭头函数的父级是谁呢?

是 o 对象

可是 o 对象不是一个函数做用域,没有 this,因此继续往上查找,而后找到了 person 函数,因此 getval 的 this 与 person 函数 this 一致的。

那么 person 函数的 this 指向谁呢?

咱们增长点 log 来增强理解:

var flag=996;
function person(fg){
    let o = new Object();
    o.flag = fg;
    o.getval=()=>{
        console.log(this);
    }
    this.a = 1;
    console.log("^^^^^^");
    console.log(this);
    console.log("^^^^^^");
    return o;
}
var pp = new person("251")
pp.getval();
console.log("&&&&&&&");
console.log(pp);
console.log("&&&&&&&");

// 输出结果以下:
^^^^^^
person {a1}
^^^^^^
person {a1}
&&&&&&&
{flag"251"getval: ƒ}
&&&&&&&
复制代码

这里 getval 函数是箭头函数,咱们知道,始终与父级的 person 的 this 保持一致,这里 person 的 this 设置了 a = 1,因此只打印了 {a:1}。

而 person 函数里 return o,是函数的返回值,这里实际上被 new 命令返回 o 给 pp 实例了。

咱们看到输出 pp 实例,是 {flag: "251", getval: ƒ}。

那 person 的 this 与 pp 实例的 this 有啥区别呢?

这里的关键知识点是:new 操做符

因为 person 函数返回的是一个对象(null 除外),因此在 new 的时候返回的是 person 函数返回的 o 对象,并无返回 person 函数的 this 给实例对象。

这里若是 person 函数返回的是一个 [数字、字符串、布尔等],那么 new 的时候,会忽略返回值,而是仍然会返回 person 的 this 给实例对象。

这也是为啥这里输出的 pp 实例不包含 person 函数内定义的 this.a。

而箭头函数的 this 指向 person 的 this ,输出了 this.a=1 可是确不包含 o 对象。

总结:这里箭头函数的 this 永远指向的是父级的 person 的 this,而不是这里的 pp 实例

例子:

var flag=996;
function person(fg){
    let o = new Object();
    o.flag = fg;
    o.getval=()=>{
        console.log(this);
    }
    this.a = 1;
    return true;
}
var pp = new person("251");
console.log(pp.a);// 1
复制代码

这里在 new person 的时候,person 构造函数返回的不是对象,因此直接忽略。

箭头函数

两个要点:

  • 箭头函数中,call 和 apply 会忽略掉 this 参数。( MDN 描述)

这实际上是“表象”,其实是由于箭头函数的 this 是指向父级的 this,由于箭头函数自身没有 this,因此没法修改自身的 this,从而言之 “忽略”。

  • 箭头函数的 this ,永远是跟随父级的 this 的。

箭头函数的 this 是从当前箭头函数逐级向上查找 this,若是找到了,则做为本身的 this 指向,若是没有则继续向上查找。而父级的 this 是可变的,因此箭头函数的 this 也可跟随父级而改变。

所以,想修改箭头函数“自己”的 this 是作不到的。

可是能够采用变动父级的 this 来达到变动子箭头函数的 this。

验证例子

function outer(){
    var inner = function(){
        var obj = {};
        obj.getVal=()=>{
            console.log("*******");
            console.log(this);
            console.log("*******");
        }
        return obj;
    };
    return inner; 
}
outer()().getVal();
// 输出以下
*******
Window {parent: Window, openernulltop: Window, length0frames: Window, …}
*******
复制代码

getVal 函数是箭头函数,方法里面的 this 是跟着父级的 this。

在 outer() 执行后,返回闭包函数 inner

而后执行闭包函数 inner,而闭包函数的 inner 仍然遵循 [谁调用,指向谁],这里没有直接调用对象,而是最外层的“省略的” window 调用的,因此 inner 的 this 是指向 window 的。

闭包函数的做用域与父级的函数做用域是一致的,咱们能够理解为闭包函数做用域已经跳出父函数了,可是还能够直接访问父函数内的变量参数等(这就是闭包的强大之处了)。

这里的 inner 实际上与 outer 的做用域一致。

改变箭头函数的 this

咱们可使用 Bind 改变父级 inner 函数的 this,来达到改变子箭头函数 getVal 的 this 指向。

例子:

function outer(){
    var inner = function(){
        var obj = {};
        obj.getVal=()=>{
            console.log("*******");
            console.log(this);
            console.log("*******");
        }
        return obj;
    };
    return inner; 
}
outer().bind(outer)().getVal();
//输出以下
*******
ƒ outer(){
    var inner = function(){
        var obj = {};
        obj.getVal=()=>{
            console.log("*******");
            console.log(this);
            console.log("*******");
        }
  …
*******
复制代码

执行 outer 方法,返回 inner 函数.

而后咱们改变 inner 的 this 指针,使用 bind 将 inner 的 this 指向到 outer。

而后执行方法,咱们看到,输出的 this 是 outer 函数。这里咱们成功改变了 getVal 的 this 指向。

箭头函数的 this 已经随同父级元素的 this 的改变而改变。

阮一峰文章中的箭头函数的描述

咱们这里拷贝原文以下(开始有截图内容,是同样的):

箭头函数有几个使用注意点。

(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。

上面四点中,第一点尤为值得注意。this对象的指向是可变的,可是在箭头函数中,它是固定的。

对此有几点疑虑:

  • 函数体内的 this 对象,是定义时所在的对象

上面例子定义时所在的对象是 inner,可是执行的时候,this 指向了 outer

  • this 对象的指向是可变的,可是在箭头函数中,是固定的

上面例子定义时所在的对象是 inner,可是执行时 this 已经指向 outer,已经被改变。

结论:箭头函数的 this 并非固定的,而是牢牢跟随父级的 this 指针,若是父级 this 改变,那么子箭头函数 this 也会跟着改变。(根本缘由是箭头函数没有 this,而是在运行时使用父级的 this)。

若是《ES6 入门》中应该理解为箭头函数 this 的指向是固定的(词法做用域),都是指向父级函数,改变的只是父级函数的 this?

那感受也能够理解为 this 都是不可变的,【谁调用,指向谁】,是调用者在变化?这样和文中也有冲突,文中提到普通函数的 this 是变化的。

若有疏漏,欢迎指正~。加我我的微信号交流:lqyygyss

欢迎关注个人微信公众号,一块儿作靠谱前端!

follow-me
相关文章
相关标签/搜索