昨日,發了一篇公衆號文章:前端
原文連接:別低估本身,但,這道題,真的有點難git
在部分羣裏引發了一些討論,其中有一點是關於箭頭函數的 this 指針的問題。使用了阮一峯《ES6 入門》文章的內容來反駁。https://github.com/ruanyf/es6tutorial/blob/gh-pages/docs/function.md 爲了隱私,屏蔽掉了微信暱稱:es6
上述截圖,來自阮一峯的《ECMAScript 6 入門》:github
下面咱們就來看看箭頭函數的 this 究竟是啥樣的!web
起初,羣裏一個朋友拋出了這個疑問,爲啥這兩個輸出有差別。微信
一個是空 person,一個是有值的 person 呢?閉包
那麼咱們首先就來分析一下究竟是什麼緣由。app
let pp = new person("251");編輯器
這裏建立了一個 person 實例 pp函數
而後 pp.getval(),直接中了以前文章提到的 [誰調用,指向誰] 邏輯,pp 實例調用了 getval,因此 getval 的 this 指向 pp 實例,打印出 pp 實例內容。
這個看起來沒有人有疑問,一切都很美好。
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 {a: 1}
^^^^^^
person {a: 1}
&&&&&&&
{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 構造函數返回的不是對象,因此直接忽略。
兩個要點:
這實際上是「表象」,其實是由於箭頭函數的 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, opener: null, top: Window, length: 0, frames: Window, …}
*******
複製代碼
getVal 函數是箭頭函數,方法裏面的 this 是跟着父級的 this。
在 outer() 執行後,返回閉包函數 inner
而後執行閉包函數 inner,而閉包函數的 inner 仍然遵循 [誰調用,指向誰],這裏沒有直接調用對象,而是最外層的「省略的」 window 調用的,因此 inner 的 this 是指向 window 的。
閉包函數的做用域與父級的函數做用域是一致的,咱們能夠理解爲閉包函數做用域已經跳出父函數了,可是還能夠直接訪問父函數內的變量參數等(這就是閉包的強大之處了)。
這裏的 inner 實際上與 outer 的做用域一致。
咱們可使用 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對象的指向是可變的,可是在箭頭函數中,它是固定的。
對此有幾點疑慮:
上面例子定義時所在的對象是 inner,可是執行的時候,this 指向了 outer
上面例子定義時所在的對象是 inner,可是執行時 this 已經指向 outer,已經被改變。
結論:箭頭函數的 this 並非固定的,而是牢牢跟隨父級的 this 指針,若是父級 this 改變,那麼子箭頭函數 this 也會跟着改變。(根本緣由是箭頭函數沒有 this,而是在運行時使用父級的 this)。
若是《ES6 入門》中應該理解爲箭頭函數 this 的指向是固定的(詞法做用域),都是指向父級函數,改變的只是父級函數的 this?
那感受也能夠理解爲 this 都是不可變的,【誰調用,指向誰】,是調用者在變化?這樣和文中也有衝突,文中提到普通函數的 this 是變化的。
若有疏漏,歡迎指正~。加我我的微信號交流:lqyygyss
歡迎關注個人微信公衆號,一塊兒作靠譜前端!