個人博客地址 → this | The story of Captain,轉載請註明出處。
問:this 是什麼?javascript
答:this 是 call 方法的第一個參數,call 的第一個參數就是 this。html
完。java
就這麼簡單麼?是的。jquery
爲何這樣說?由於全部的函數/方法調用的時候均可以 轉換 爲 call 形式,call 的第一個參數顯式的指明瞭函數該次執行時候的上下文。閉包
今天咱們深刻探討一下如何肯定 this。app
this 由函數的上下文肯定。函數
上下文分爲 全局上下文(Global Context) 以及 函數上下文(Function Context)。優化
在全局中,this 一概指向 全局對象 window。例如:this
console.log(this === window); //; true
在函數中,上下文由函數被調用的方式決定。spa
以 「函數名( )」 形式調用的函數即爲簡單調用,簡單調用時上下文爲全局上下文,所以 this === window
。
舉例一:
function foo () { console.log(this === window); } foo(); // true
舉例二:
function fn1 () { function fn2 () { console.log(this === window); } fn2(); } fn1(); // true,由於 fn2 爲簡單調用
舉例三:
let obj = { fn1: function () { console.log(this === window); } }; let fn2 = obj.fn1; fn2(); // true
第三個例子中,爲何 fn2() 執行結果爲 true ?由於執行了 let fn2 = obj.fn1
以後 fn2 爲:
fn2 = function () { console.log(this); }
再執行 fn2() 時,爲簡單調用,所以 this === window
。
當函數做爲一個對象的方法被調用時,this 指向該對象。
舉例一:
let obj = { fn1: function () { console.log(this === obj); } }; obj.fn1(); // true
以 obj.fn1() 形式調用 fn1 時,是以方法形式調用的,this 指向該函數所屬的對象,即 obj。
舉例二:
let obj = { fn1: { fn2:function () { console.log(this === obj.fn1); } } }; obj.fn1.fn2(); // true
以 obj.fn1.fn2() 形式調用 fn2 時,是以方法形式調用的,this 指向該函數所屬的對象,即 obj.fn1,不少人常誤覺得此處的 this 指向 obj,這是錯誤的。
舉例三:
let obj = { fn1: function () { return function () { console.log(this === window); } } }; let fn2 = obj.fn1(); fn2(); // true
爲何 fn2() 的執行結果爲 true ?由於執行了 let fn2 = obj.fn1()
以後 fn2 爲:
fn2 = function () { console.log(this === window); }
再執行 fn2() 時,爲簡單調用,所以 this === window
。若是想要將 fn2 中的 this 指向 obj,可將指向 obj 的 this 保存在中間變量,改動以下所示:
let obj = { fn1: function () { let that = this; return function () { console.log(that === obj); } } }; let fn2 = obj.fn1(); fn2(); // true
利用 let that = this
將 fn1 中的 this 保存在 that 變量中,而後 fn2() 的結果即爲 true,固然這其中涉及到了 閉包(closure) 的知識。
如下狀況中的 this 須要進行特殊記憶。
箭頭函數(arrow function,=>),箭頭函數爲 ES6 中引入的新的函數表示法,不一樣之處在於,箭頭函數中沒有 this,箭頭函數中的 this 爲其執行上下文中的 this,如何理解?舉例說明。
舉例一:
() => console.log(this === window); // true
其執行上下文爲全局上下文,this 指向 window。
舉例二:
function foo () { return () => console.log(this === window); }; foo()(); // true
和方法調用中的舉例三相似。
舉例三:
let obj = { fn1: () => console.log(this === window); }; obj.fn1(); // true
爲何是 true ?方法調用中的舉例一中的 this 不是 obj 嗎?沒錯,箭頭函數 fn1 中是沒有本身的 this 的,所以 this 不指向 obj ,繼續向上找 obj 的上一級,直到找到有 this 的上下文爲止,obj 處在全局上下文中, 全局上下文中有 this,所以箭頭函數中的 this 爲全局上下文中的 this,即 指向 window。
舉例四:
let obj = { fn1: function () { return () => console.log(this === obj); } }; let fn2 = obj.fn1(); fn2(); // true
此處又和方法調用的舉例三不一樣,由於箭頭函數中是沒有本身的 this 的,箭頭函數中的 this 爲其上一級的 this ,所以,箭頭函數中的 this 爲其上一級,即 fn1 中的 this,fn1 中的 this 指向 obj,因此箭頭函數中的 this 指向 obj。根據箭頭函數的特性:箭頭函數中的 this 保留了其上一級的 this 指向,那麼方法調用舉例三的改動能夠優化爲本例所示,用一個箭頭函數便可解決,省去了中間變量。
當一個函數做爲構造函數使用時,構造函數的 this 指向由該構造函數 new 出來的對象。舉例說明:
function CreateNewPerson (name,gender,age) { this.name = name; this.gender = gender; this.age = age; } let me = new CreateNewPerson('daijt','male',18); console.log(me.name); // 'daijt' console.log(me.gender); // 'male' console.log(me.age); // 18
執行 let me = new CreateNewPerson('daijt','male',18)
時,構造函數中的 this 直接指向由其 new 出來對象對象 me ,所以執行完該句後 me 的結構以下:
me = { name: 'daijt', gender: 'male', age: 18 }
舉例一:
let name = new String('daijt'); name.toUpperCase(); // DAIJT
根據上文構造函數中的 this,執行 let name = new String('daijt')
時,String 構造函數中的 this 指向了 name,而 name 有 __proto__ 屬性,該屬性指向全部 string 類的共有屬性或者方法,而這些共有的屬性和方法都保存在 String.prototype 中,即:
name.__proto__ === String.prototype; // true
所以 name 是有 toUpperCase 方法的(原型鏈繼承而來),調用 toUpperCase 時,toUpperCase 中的 this 指向 name,所以 name.toUpperCase()
的結果爲 DAIJT
。
舉例二:
let name = 'daijt'; name.toUpperCase.(); // DAIJT
爲什麼沒有經過 new 出來的對象也具備 toUpperCase 方法呢?由於在執行 let name = 'daijt'
的過程當中,JS 有一個臨時轉化的過程,例如:
let name = (function (string) { return new String(string); })('daijt');
所以,name 也繼承了 string 類共有的屬性和方法,這也算是 JS 的一個語法糖吧。 固然,這涉及到了其餘的知識。
舉例:
let buttons = document.querySelector('button'); buttons.addEventListener('click', function (event) { console.log(this === event.currentTarget); // true });
使用 addEventListener 綁定 DOM 時,監聽函數中的 this 指向觸發事件的 currentTarget,currentTarget 表示被綁定了監聽函數的 DOM 元素。
注意:若是是經過冒泡觸發監聽函數的話, event.target 不必定等於 event.currentTarget 。
HTML:
<ul id="father-ul"> <li class='father-li'>father-ul的第1個li</li> <li class='father-li'>father-ul的第2個li <ul> <li>son-ul的第1個li</li> <li>son-ul的第2個li</li> <li>son-ul的第3個li</li> </ul> </li> <li class='father-li'>father-ul的第3個li</li> </ul>
JavaSctipt:
$('#father-ul').on('click', '.father-li', function (event) { console.log(event.target); console.log(event.currentTarget); console.log(this === currentTarget); });
當點擊 <li class='father-li'>father-ul的第1個li</li>
時,控制檯打印出:
<li class='father-li'>father-ul的第1個li</li> <li class='father-li'>father-ul的第1個li</li> true
當點擊 <li>son-ul的第2個li</li>
時,控制檯打印出:
<li>son-ul的第2個li</li> <li class='father-li'>father-ul的第2個li <ul> <li>son-ul的第1個li</li> <li>son-ul的第2個li</li> <li>son-ul的第3個li</li> </ul> </li> true
所以能夠得出結論:jQuery EventHandle 中的 this 指的是被代理事件監聽的 DOM 元素,也就是匹配全部選擇器的 DOM 元素,即 .father-li
,具體解釋可參照 jQuery 文檔 。
### 如何改變 this
以上所述的 this 都爲肯定的 this,那麼如何本身設置 this,改變 this 的指向呢?或者說如何動態改變上下文呢?ES5 爲咱們提供了三個全局方法:call()、apply()、bind()。三個方法均可以動態的改變上下文,即 this 的指向,三者的區別能夠參照 MDN,以 call() 爲例進行說明。
var name = '全局上下文'; let me = { name: 'daijt', gender: 'male'. age: 23, }; let myGirlFriend = { name: 'xiaofang', gender: 'female', age: 18 }; function printName() { console.log(this.name); } printName(); // window printName.call(me); // daijt printName.call(myGirlFriend); // xiaofang
printName()
時:簡單調用,所以其內部的 this 指向 全局上下文,所以 this === window
,而使用 var 關鍵字在全局聲明的變量會做爲 window 對象的屬性,所以 this.name === window.name === 全局上下文
。
printName.call(me)
時:由於 call() 的第一個參數爲 thisArg ,所以使用 call() 顯式的指明瞭 printName 函數本次執行的上下文,即 me,因 this 指向上下文,因此 this === me
,this.name === me.name === daijt
。
printName.call(myGirlFriend)
與執行 printName.call(me)
同理。回到本文開頭,全部的函數/方法調用的時候均可以 轉換 爲 call 形式,call 的第一個參數顯式的指明瞭函數該次執行時候的上下文,這就是判斷 this 指向的技巧,以代碼爲例進行演示:
舉例一:
function foo () { console.log(this); } foo(); // window foo.call(); // window // non-strict mode foo.call(undefined); // window // strict mode foo.call(undefined); // undefined
this === window
。this === window
,在全局上下文中,非嚴格模式 下,undefined 即爲 window ,嚴格模式 下,undefined 不能指代 window ,因此嚴格模式下 this === undefined
。舉例二:
let obj = { fn1: function () { console.log(this === obj); } }; obj.fn1(); // true obj.fn1.call(obj); // true
舉例三:
let obj = { fn1: { fn2:function () { console.log(this === obj.fn1); } } }; obj.fn1.fn2(); // true obj.fn1.fn2.call(obj.fn1); // true
舉例四:
let obj = { fn1: function () { return function () { console.log(this === window); } } }; let fn2 = obj.fn1(); fn2(); // true fn2.call(); // true obj.fn1.call(obj).call(undefined); // true
以上三個例子中,如何判斷傳給 call() 的 this 呢?以舉例四的最後一句代碼爲例進行分析:
經過這張 call() 的圖解,this 應該徹底掌握了,因此將函數的調用改寫爲 call() 形式是最直接明瞭判斷 this 的方法。
看到這裏,你搞懂 this 了嗎?
參考連接:
更多精彩內容,請點擊個人博客 → The story of Captain