JavaScript?this指向綁定方式及不適用情況詳解
前言
JavaScript 中的 this 指向問(wèn)題對(duì)于 web 前端入行不深的人來(lái)說(shuō)是個(gè)比較復(fù)雜的問(wèn)題。特寫(xiě)此文章來(lái)記錄最近遇到的關(guān)于匿名函數(shù)中 this 指向問(wèn)題的思考和感悟。
問(wèn)題復(fù)現(xiàn)
最近在研究函數(shù)防抖場(chǎng)景時(shí)看到如下代碼:
function debounce(fn, delay) { var timer; // 維護(hù)一個(gè) timer return function () { var _this = this; // 取 debounce 執(zhí)行作用域的 this var args = arguments; if (timer) { clearTimeout(timer); } timer = setTimeout(function () { fn.apply(_this, args); // 用 apply 指向調(diào)用 debounce 的對(duì)象,相當(dāng)于 _this.fn(args); }, delay); }; }
其中有一段代碼是 var _this = this
這段代碼出現(xiàn)在由 return 返回的匿名函數(shù)中,這個(gè)時(shí)候我就有些懵逼了,因?yàn)楦鶕?jù)我匱乏的 js 知識(shí),這里的 this 應(yīng)該是指向全局作用域才對(duì),為什么能像注釋那樣指向 debounce 執(zhí)行時(shí)的作用域呢?感覺(jué)如下所寫(xiě)是否更加合理呢?(事實(shí)證明這么寫(xiě)肯定是不對(duì)的)
function debounce(fn, delay) { var timer var _this = this return function() { ... } }
于是我打算用代碼來(lái)實(shí)測(cè)這里的 debounce 執(zhí)行作用域中的 this 到底指的是什么,它會(huì)變化嗎?還是根據(jù)我的理解只要是像這樣類似的匿名函數(shù),其中的 this 都是指向全局的呢? 于是我寫(xiě)下如下代碼(關(guān)鍵部分):
body 部分新增一個(gè) button 標(biāo)簽
<button>我是button</button>
script 標(biāo)簽內(nèi)部代碼如下:
//函數(shù)防抖 function debounce(fn, delay) { var timer; // 維護(hù)一個(gè) timer return function () { var _this = this; // 取 debounce 執(zhí)行作用域的 this var args = arguments; if (timer) { clearTimeout(timer); } timer = setTimeout(function () { fn.apply(_this, args); // 用 apply 指向調(diào)用 debounce 的對(duì)象,相當(dāng)于 _this.fn(args); }, delay); }; } var btn = document.getElementsByTagName('button')[0] btn.onclick= debounce(function() { console.log(this) }, 1000)
點(diǎn)擊按鈕,看看控制臺(tái)輸出 this 到底是誰(shuí),按照我之前的理解輸出的 this 應(yīng)該是window 全局對(duì)象才對(duì)
<button>我是button</button>
出乎意料,這里的 this 輸出的是 button 元素,于是我再在上述腳本中新增一個(gè)事件綁定:
window.onclick = debounce(function() { console.log(this) }, 1000)
點(diǎn)擊頁(yè)面空白處輸入如下:
這次輸出的就是 window 了! 看來(lái)這里的 this 實(shí)際是跟 debounce 函數(shù)所返回函數(shù)的實(shí)際調(diào)用者有關(guān),第一次控制臺(tái)輸出的是 button 元素,因?yàn)槭峭ㄟ^(guò) button 元素來(lái)調(diào)用該返回函數(shù),第二次調(diào)用者就是 widnow,舉這段
btn.onclick = dobounce(function() {console.log(this)}, 1000)
代碼的例子:
- 頁(yè)面初始化完畢后,執(zhí)行腳本代碼,debounce 函數(shù)接收一個(gè)具體函數(shù)(將其命名為 fn 好了)和一個(gè)時(shí)間間隔參數(shù)( intervcal )
- 進(jìn)到 debounce 代碼內(nèi)部,return 一個(gè)匿名函數(shù),并賦給 btn.onclick,實(shí)際上就是事件綁定
- 所以說(shuō)當(dāng)我點(diǎn)擊 button 的時(shí)候,btn.onclick 的執(zhí)行代碼是這樣的:
btn.onclick = function() { var _this = this; // 取 debounce 執(zhí)行作用域的 this var args = arguments; if (timer) { clearTimeout(timer); } //因?yàn)殚]包的存在 timer 還是取的 debounce 中的 timer timer = setTimeout(function () { fn.apply(_this, args); // 用 apply 指向調(diào)用 debounce 的對(duì)象,相當(dāng)于 _this.fn(args); }, delay); }
那么這里的 this 指向的就是 button 元素了,為什么呢,以上的例子引出我們今天的主題 - 函數(shù)的 this 指向
調(diào)用位置
關(guān)于函數(shù)的 this ,常常有句話,叫做誰(shuí)調(diào)用就指向誰(shuí)
。簡(jiǎn)單來(lái)說(shuō) this 的指向跟函數(shù)的調(diào)用位置緊密相關(guān),要想知道函數(shù)調(diào)用時(shí) this 到底引用了什么,就應(yīng)該明確函數(shù)的調(diào)用位置。一般來(lái)說(shuō)需要通過(guò)函數(shù)的調(diào)用棧來(lái)判斷來(lái)分析出函數(shù)真正的調(diào)用位置,具體怎么分析呢?除了目測(cè)代碼外,還也可以借用瀏覽器的開(kāi)發(fā)者工具( debug 工具),去推斷目標(biāo)函數(shù)到底是在哪里調(diào)用的,這樣才能更準(zhǔn)確的知曉this的指向。比如下面這段代碼:
function foo() { console.log('foo') } function bar() { console.log('bar') foo() } bar()
要想知道 foo 函數(shù)是由誰(shuí)調(diào)用的,就可以在瀏覽器中打開(kāi)調(diào)試工具,在 foo 函數(shù)中的第一行打一個(gè)斷點(diǎn),找到函數(shù)的調(diào)用棧,然后再找到棧中的第二個(gè)元素,這就是真正的調(diào)用位置。如下圖所示:
從瀏覽器的調(diào)試工具可以找到 foo 函數(shù)的真正調(diào)用位置。
默認(rèn)綁定
var a = 2 function foo() { var a = 3 console.log(this.a) } foo() // 2
輸出結(jié)果為 2。因?yàn)?foo 函數(shù)調(diào)用時(shí)處于全局環(huán)境下(這里是 window ),查看一下瀏覽器中的調(diào)用棧:
調(diào)用棧中只有 foo 函數(shù)一個(gè)元素,說(shuō)明調(diào)用者就是當(dāng)前的全局環(huán)境 window ,所以這里的 this 指向的就是 window,因?yàn)樽钔獠康?a 一開(kāi)始是最為 window.a 聲明并賦值的,所以可以理解為this = window; this.a = 2
。比較特殊的一點(diǎn)就是,如果在 foo 函數(shù)內(nèi)部采用了嚴(yán)格模式,那么 this 就會(huì)綁定到 undefined:
var a = 2 function foo() { 'use strict' var a = 3 console.log(this.a) } foo() //`//Cannot read property 'a' of undefined`
隱式綁定
舉如下代碼為例:
var a = 2 function foo() { console.log(this.a) } var obj1 = { a:3, foo: foo } obj.foo() //3
輸出結(jié)果為 3,說(shuō)明這里的 this 指向的是 obj1,為什么不再是指向全局環(huán)境了呢。在這里就要考慮到調(diào)用位置是否存在上下文對(duì)象,或者說(shuō)是否被某個(gè)對(duì)象擁有或包含。在上述的代碼中,foo 函數(shù)的引用被賦給了 obj1 的 foo 屬性obj1.foo = foo
, 并且在 foo 函數(shù)被調(diào)用時(shí),它的前面也加上了對(duì) obj1 的引用。此時(shí),當(dāng)函數(shù)引用有上下文對(duì)象時(shí),隱式綁定規(guī)則就會(huì)將函數(shù)中的this綁定到這個(gè)上下文對(duì)象,這里的上下文對(duì)象就是 obj1。 其實(shí)在理解上下文對(duì)象時(shí),個(gè)人覺(jué)得不用那么抽象,它無(wú)非就是一個(gè)不確定的代名詞,簡(jiǎn)單來(lái)說(shuō)你覺(jué)得它是什么,那它就是什么。
顯式綁定
默認(rèn)綁定和隱式綁定在我看來(lái)是 js 的一個(gè)內(nèi)置且被動(dòng)的綁定方式,就是已經(jīng)這么幫你設(shè)定好了,只要符合這兩個(gè)規(guī)則且沒(méi)有其他規(guī)則存在那么 this 的指向就按照這兩個(gè)規(guī)則來(lái)。顯然,這類被動(dòng)的綁定方式并不符合實(shí)際的代碼編寫(xiě)需要,比如我要指定一個(gè)函數(shù)的 this ,該怎么辦呢?這時(shí)候就需要顯式綁定了。call、apply 會(huì)在顯式綁定時(shí)發(fā)揮作用。參考如下代碼:
function foo() { console.log(this.a) } var obj1 = { a: 2 } var a = 3 foo.call(obj1) // 2
輸出結(jié)果為 2。原因是因?yàn)?call 方法改變了 foo 函數(shù)運(yùn)行的 this 指向,將原本 this 指向的 window 全局轉(zhuǎn)為了指向 obj1 ,所以輸出的是 2,從這里也可以看出,顯示綁定的優(yōu)先級(jí)大于默認(rèn)綁定。
new 綁定
首先應(yīng)該明確一點(diǎn),JavaScript 中的 new 與其他面向類的語(yǔ)言不同,在 js 中 new 后面的只不過(guò)是一個(gè)普通的函數(shù),僅僅是被 new 操作符調(diào)用了而已。使用 new 調(diào)用函數(shù)時(shí),會(huì)執(zhí)行如下步驟:
- 創(chuàng)建(或者說(shuō)構(gòu)造)一個(gè)全新的對(duì)象。
- 這個(gè)新對(duì)象會(huì)被執(zhí)行 [[Prototype]] 連接。
- 這個(gè)新對(duì)象會(huì)綁定到函數(shù)調(diào)用的 this。
- 如果函數(shù)沒(méi)有返回其他對(duì)象,那么 new 表達(dá)式中的函數(shù)調(diào)用會(huì)自動(dòng)返回這個(gè)新對(duì)象。 代碼如下所示:
function foo(a) { this.a = a } var bar = new foo(2) console.log(bar.a) // 2
輸出結(jié)果為 2。
不適用的情況
ES6 中出現(xiàn)了一種特殊的函數(shù):箭頭函數(shù)。以上的四種規(guī)則在箭頭函數(shù)中都不適用,箭頭函數(shù)的是根據(jù)外層函數(shù)或者全局鏈決定 this 的。其實(shí)這也是對(duì)以往 ES6 之前的較為復(fù)雜的 this 綁定規(guī)則的優(yōu)化和統(tǒng)一,在實(shí)際編碼的過(guò)程中更容易讓人理解,當(dāng)然箭頭函數(shù)也有缺點(diǎn),這里就不再展開(kāi)。
總結(jié)
在寫(xiě)這篇總結(jié)文章之前,一直對(duì) js 中的 this 問(wèn)題理解不深,翻了幾遍《你不知道的JavaScript》才算真正有所學(xué)習(xí)和領(lǐng)悟。本文寫(xiě)的并不具體,就 this 綁定時(shí)的綁定丟失問(wèn)題并沒(méi)有展開(kāi)敘述,綁定的規(guī)則優(yōu)先級(jí)也沒(méi)有寫(xiě)全,暫時(shí)先留個(gè)坑,后面再來(lái)填坑 !
參考文獻(xiàn)
以上就是JavaScript this指向綁定方式及不適用情況詳解的詳細(xì)內(nèi)容,更多關(guān)于JavaScript this指向的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Project?Reference優(yōu)化TypeScript編譯性能示例
這篇文章主要為大家介紹了Project?Reference優(yōu)化TypeScript編譯性能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08mitt tiny-emitter發(fā)布訂閱應(yīng)用場(chǎng)景源碼解析
這篇文章主要為大家介紹了mitt tiny-emitter發(fā)布訂閱應(yīng)用場(chǎng)景源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12JavaScript前端實(shí)現(xiàn)小說(shuō)分頁(yè)功能示例
這篇文章主要為大家介紹了JavaScript前端實(shí)現(xiàn)小說(shuō)分頁(yè)功能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07微信小程序 網(wǎng)絡(luò)請(qǐng)求(post請(qǐng)求,get請(qǐng)求)
這篇文章主要介紹了微信小程序 網(wǎng)絡(luò)請(qǐng)求(post請(qǐng)求,get請(qǐng)求)的相關(guān)資料,需要的朋友可以參考下2017-01-01微信小程序 開(kāi)發(fā)經(jīng)驗(yàn)整理
這篇文章主要介紹了微信小程序 開(kāi)發(fā)經(jīng)驗(yàn)整理的相關(guān)資料,需要的朋友可以參考下2017-02-02微信小程序組件 contact-button(客服會(huì)話按鈕)詳解及實(shí)例代碼
這篇文章主要介紹了微信小程序組件 contact-button(客服會(huì)話按鈕)詳解及實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2017-01-01