面向對象編程語言中 this 是一個很是重要的關鍵字,其在函數執行中經過 this 來明確操做的對象,在 JS 中,JS 並不是面嚮對象語言,可是他也有 this 關鍵字,用來指向函數的調用對象,博主在學習 es5 的時候對 this 理解很是容易,由於之前學過面嚮對象語言,也曾大量的使用,可是學習到 es6 的箭頭函數,它的 this 指向就讓我有點困惑,因而花了一些時間從各個技術博客, MDN 文檔,還有一些其餘的資料進行了研究,總算是對箭頭函數的 this 指向有個深入的認識了,下面就會講述下我本身對箭頭函數 this 的理解。前端
this 在編程語言中用的次數很是多,多到你不知不覺就會下意識的寫出 this,好比事件綁定,對事件源的操做,好比對一個對象的相關屬性操做,繼承中的 this 使用等。因此 this 的指向是一個一直須要理解並掌握的一個知識,若是不清楚 this 的指向,那麼不少方法就會出現大問題,而且非語法的 bug 維護起來更是使人頭大。嚴格模式中的 this 在全局中指向 undefined,其餘地方下並無什麼影響,因此一下論點也只討論在非嚴格模式下的 this 指向。es6
普通函數中的 this 很好理解,無非如下四點:編程
總結一句話就是誰調用函數,this 就指向誰,普通函數的 this 取決於執行時的函數。app
下面重點介紹下箭頭函數中的 this 問題編程語言
箭頭函數的語法:函數
// 無參數直接輸出一句話
var fun1 = () => console.log('hello');
// 有一個參數並返回 x*x
var fun2 = x => x*x;
// 有一個參數並返回 y*y
var fun3 = (y) => y*y;
// 有兩個參數並返回 x+y,也能夠簡寫 (x,y) => x+y;
var fun4 = (x,y) => {
return x+y;
}
複製代碼
若是箭頭函數中沒有用到 this 的話,那麼大可放心的直接使用,由於代碼寫的更少更方便,但若是須要 this,那麼必定得清楚箭頭函數中 this 指向誰。箭頭函數的 this 指向也有不一樣的說法,下面列舉出不一樣說法。學習
第一種說法較爲模糊,概念不是那麼的清晰,第二種說法經過繼承而來有點牽強的感受,並且我我的覺的有點誤導的感受,所以我看到第三種說法時,雖然以爲有點陌生,說法很是官方的感受,可是卻以爲自習深刻了解第三種說法,應該能徹底掌握箭頭函數的 this,因此就仔細研究了一下,下面將展開對第三種說法的論點,也是本篇的核心(前面一堆廢話,湊字數:huaji:)ui
詞法做用域簡單來講指的是函數做用域的一種工做模式,因此詞法做用域的法則是基於做用域的概念。ES6 以前做用域分爲全局做用域、局部做用域,變量遵循詞法做用域,ES6 引入了塊級做用域,使得JS也能像其餘的編程語言有了真正的塊級代碼。執行上下文其實就是執行環境,也就是當前的 this,這裏有點繞了吧,其實不要緊,下面會說明的,只不過此時是把上面的第二種繼承方式的原理說明了,箭頭函數的 this 就取決於這個執行上下文的 this,所以才說他是經過繼承而來。this
有了上面的知識做爲根基,那麼究竟怎麼理解此法做用域,和執行上下文,以及若是肯定箭頭函數的 this 指向,接下來繼續說明。 先來一段代碼:es5
var num = 100;
var obj = {
fun1: function () {
num = 200;
console.log(num);
},
fun2: function () {
var num = 300;
console.log(num)
}
}
obj.fun1(); // No.2 200
obj.fun2(); // No.3 300
console.log(num); // No.1 200
複製代碼
從上述代碼中,fun1 執行時爲 num 賦值,可是能夠從全局中尋找到 num,所以對全局的 num 進行操做,fun2 執行時在本身的局部做用域(函數)聲明瞭一個 num,此時的 num 爲局部的,與全局的 num 無關,fun2 執行完畢後局部 num 就消失了,因此全局的 num 最終結果爲200,這一段代碼中的變量使用的法則,遵循的就是此法做用域,說白點就是尋找變量的過程和其生命週期的範圍受此法做用域約束,另外一個隱藏的知識點就是 obj.fun1(),obj.fun2() 執行時的執行上下文就是 obj, this 就是obj,執行環境就是 obj。
如今真正進入箭頭函數的 this 討論,若是有點忘了箭頭函數 this 指向的第三種說明,如今能夠立馬向上翻滾看一下 :huaji:
看以下 Demo:
var obj = {};
var fun1 = function () {
console.log(this);
}
var fun2 = () => {
console.log(this);
}
console.log('normal------------');
// 全局環境下直接調用
fun1();
fun2();
console.log('call------------');
// 經過 call 進行執行環境的綁定
fun1.call(obj);
fun2.call(obj);
複製代碼
再看下一段代碼:
var obj = {
fun1: function () {
console.log(this);
},
fun2: () => {
console.log(this);
}
}
obj.fun1();
obj.fun2();
複製代碼
obj.fun1();
obj.fun2();
obj.fun2.call(obj);
複製代碼
箭頭函數的 this 指向也遵循**詞法做用域**,指向當前環境的**執行上下文**
再來一段代碼鞏固下:
var obj = {
fun1: function () {
setTimeout(function () {
console.log('普通函數', this);
})
},
fun2: function () {
setTimeout(() => {
console.log('箭頭函數', this);
})
}
}
obj.fun1();
obj.fun2();
複製代碼
再來最後一段代碼:
var obj = {
// 普通函數中定義一個當即執行函數輸出 this
fun1: function () {
(function () {
console.log(this);
})();
},
// 普通函數中定義一個當即執行箭頭函數輸出 this
fun2: function () {
(() => {
console.log(this);
})();
},
// 箭頭函數中定義一個當即執行的普通函數輸出 this
fun3: () => {
(function () {
console.log(this);
})();
},
// 箭頭函數中定義一個當即執行的箭頭函數輸出 this
fun4: () => {
(() => {
console.log(this);
})();
}
}
複製代碼
console.log('正常執行------------');
obj.fun1(); // window
obj.fun2(); // obj
obj.fun3(); // window
obj.fun4(); // window
複製代碼
// 下面 this 是全局的 window
console.log('使用call執行------------');
obj.fun1.call(this); // window
obj.fun2.call(this); // window
obj.fun3.call(this); // window
obj.fun4.call(this); // window
複製代碼
fun2: function () {
var testFun = () => {
console.log(this);
}
testFun();
}
複製代碼
再次根據箭頭函數 this 綁定的法則來看(箭頭函數的 this 遵循詞法做用域,指向其所屬環境的執行上下文(也能夠說是宿主對象)。),每當 fun2 被調用時,會從新定義箭頭函數,當前箭頭函數的詞法做用域是 fun2,其指向 fun2 的執行上下文,正常狀況是 obj,但咱們經過 obj.fun2.call(this) 強行改變了 fun2 的執行上下文爲 window,因此 fun2 的箭頭函數從新定義時則指向了 fun2 的執行上下文 window,也就是經過 call 的結果,因此這並不矛盾。 驗證代碼以下:
var obj = {
// 普通函數中定義一個當即執行箭頭函數輸出 this
fun2: function () {
(() => {
console.log(this); // 正常調用 fun2 時,this 已經給被綁定爲 obj 了
}).call(window); // 沒法經過 call 強行綁定 window
},
}
obj.fun2();
複製代碼
若是讀者看到哪裏有誤或哪裏說的模糊還請說明,我會及時學習更正的,我也只是一個剛入門的前端小白,這也只是個人我的理解,也是第一次在掘金髮布的本身文章,但願大佬們不要嫌棄我