對阮一峯《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
相關文章
相關標籤/搜索