摘要:但是有一個(gè)總的原則那就是總會(huì)指向,調(diào)用函數(shù)的那個(gè)對(duì)象。作為對(duì)象方法的調(diào)用函數(shù)作為某個(gè)對(duì)象的方法調(diào)用,這時(shí)就指這個(gè)上級(jí)對(duì)象。
這是前端面試題系列的第 4 篇,你可能錯(cuò)過(guò)了前面的篇章,可以在這里找到:
偽類與偽元素的區(qū)別及實(shí)戰(zhàn)
如何實(shí)現(xiàn)一個(gè)圣杯布局?
今日頭條 面試題和思路解析
在前端的面試中,經(jīng)常會(huì)問(wèn)到有關(guān) this 的指向問(wèn)題。最近,朋友Z 向我求助說(shuō),他一看到 this 的題目就犯難,搞不清楚 this 究竟指向了誰(shuí)。我為他做了解答,并整理成了這篇文章,希望能幫到有需要的同學(xué)。
一道面試題朋友Z 給我看了這樣一道題:
var length = 10; function fn () { console.log(this.length); } var obj = { length: 5, method: function (fn) { fn(); arguments[0](); } }; obj.method(fn, 1);
問(wèn):瀏覽器的輸出結(jié)果是什么?
它的答案是:先輸出一個(gè) 10,然后輸出一個(gè) 2。
讓我們來(lái)解析一下原因:
在我們這道題中,雖然 fn 作為 method 的參數(shù)傳了進(jìn)來(lái),但它的調(diào)用者并不受影響,任然是 window,所以輸出了 10。
arguments[0]();這條語(yǔ)句并不常見(jiàn),可能大家有疑惑的點(diǎn)在這里。 其實(shí),arguments 是一種特殊的對(duì)象。在函數(shù)中,我們無(wú)需指出參數(shù)名,就能訪問(wèn)??梢哉J(rèn)為它是一種,隱式的傳參形式。
當(dāng)執(zhí)行 arguments[0](); 時(shí),其實(shí)調(diào)用了 fn()。而這時(shí),fn 函數(shù)中 this 就指向了 arguments,這個(gè)特殊的對(duì)象。obj.method 方法接收了 2 個(gè)參數(shù),所以 arguments 的 length,很顯然就是 2 了。
改造一下再來(lái),不少同學(xué)對(duì) this 的指向感到疑惑,是因?yàn)?this 并沒(méi)有指向我們預(yù)期的那個(gè)對(duì)象。
就像這道題,從語(yǔ)義上來(lái)看,我們期望 fn() 輸出的是 obj 自己的 length,也就是 5,而不是 10。那么如果要得到 5 的結(jié)果,我們?cè)撊绾涡薷倪@段代碼呢?
其實(shí)只要多做一步處理就好。就是讓 this 指向 obj 自己。這里,我們可以用 call 來(lái)改變 this 的指向,像下面這樣:
var length = 10; function fn () { console.log(this.length); } var obj = { length: 5, method: function (fn) { // 在這里用call 將 this 指向 obj 自己 fn.call(this); } }; obj.method(fn);
輸出的結(jié)果就是 5 了,搞定。
看吧,this 也沒(méi)那么復(fù)雜吧,我們只需要一些簡(jiǎn)單的操作,就能控制 this 的指向了。那么,問(wèn)題來(lái)了,為什么有時(shí)候 this 會(huì)失控呢?
其實(shí),這與 this 機(jī)制背后的原理有關(guān)。不過(guò)別急,讓我們從理解 this 的基本概念開(kāi)始,先來(lái)看看 this 到底是什么?
this 是什么?this 是 JavaScript 中的一個(gè)關(guān)鍵字。它通常被運(yùn)用于函數(shù)體內(nèi),依賴于函數(shù)調(diào)用的上下文條件,與函數(shù)被調(diào)用的方式有關(guān)。它指向誰(shuí),則完全是由函數(shù)被調(diào)用的調(diào)用點(diǎn)來(lái)決定的。
所以,this,是在運(yùn)行時(shí)綁定的,而與編寫(xiě)時(shí)的綁定無(wú)關(guān)。隨著函數(shù)使用場(chǎng)合的不同,this 的值也會(huì)發(fā)生變化。但是有一個(gè)總的原則:那就是this 總會(huì)指向,調(diào)用函數(shù)的那個(gè)對(duì)象。
為什么要用this?從概念上理解起來(lái),似乎有點(diǎn)費(fèi)勁。那我們?yōu)槭裁催€要使用 this 呢?用了 this 會(huì)帶來(lái)什么好處?
讓我們先看下面這個(gè)例子:
function identify() { return this.name.toUpperCase(); } var me = { name: "Kyle" }; var you = { name: "Reader" }; identify.call( me ); // KYLE identify.call( you ); // READER
一開(kāi)始我們可能太不明白為何這樣輸出。那不如先換個(gè)思路,與使用 this 相反,我們可以明確地將環(huán)境對(duì)象,傳遞給 identify()。像這樣:
function identify(context) { return context.name.toUpperCase(); } identify( you ); // READER
在這個(gè)簡(jiǎn)單的例子中,結(jié)果是一樣的。我們可以把環(huán)境對(duì)象直接傳入函數(shù),這樣看來(lái)比較直觀。但是,當(dāng)模式越發(fā)復(fù)雜時(shí),將執(zhí)行環(huán)境作為一個(gè)明確的參數(shù)傳遞給函數(shù),就會(huì)顯得非常混亂了。
而 this 機(jī)制,可以提供一種更優(yōu)雅的方式,來(lái)隱含地“傳遞”一個(gè)對(duì)象的引用,這會(huì)使得 API 的設(shè)計(jì)更加地干凈,復(fù)用也會(huì)變得容易。
this 的原理明白了 this 的概念之后,不經(jīng)讓我好奇,為何 this 指向的就是函數(shù)運(yùn)的執(zhí)行環(huán)境呢?
之前,看到了 阮老師 的一篇文章,十分透徹地分析了 this 的原理。我根據(jù)自己的理解,整理如下。
很多教科書(shū)會(huì)告訴你,this 指的是函數(shù)運(yùn)行時(shí)所在的環(huán)境。但是,為什么會(huì)這樣?也就是說(shuō),函數(shù)的運(yùn)行環(huán)境到底是怎么決定的?
理解 this 的原理,有助于幫我們更好地理解它的用法。JavaScript 語(yǔ)言之所以有 this 的設(shè)計(jì),跟內(nèi)存里面的數(shù)據(jù)結(jié)構(gòu)有關(guān)系。
來(lái)看一個(gè)簡(jiǎn)單的示例:
var obj = { foo: 5 };
上面的代碼將一個(gè)對(duì)象賦值給變量 obj。JavaScript 引擎會(huì)先在內(nèi)存里面,生成一個(gè)對(duì)象 { foo: 5 },然后把這個(gè)對(duì)象的內(nèi)存地址賦值給變量 obj。
也就是說(shuō),變量 obj 其實(shí)只是一個(gè)地址。后面如果要讀取 obj.foo,引擎先從 obj 拿到內(nèi)存地址,然后再?gòu)脑摰刂纷x出原始的對(duì)象,返回它的 foo 屬性。
這樣的結(jié)構(gòu)很清晰,但如果屬性的值是一個(gè)函數(shù),又會(huì)怎么樣呢?比如這樣:
var obj = { foo: function () {} };
這時(shí),JavaScript 引擎會(huì)將函數(shù)多帶帶保存在內(nèi)存中,然后再將函數(shù)的地址賦值給 foo 屬性的 value 屬性。
可以看到,函數(shù)是一個(gè)多帶帶的值(以地址形式賦值),所以才可以在不同的環(huán)境中執(zhí)行。
又因?yàn)?,JavaScript 允許在函數(shù)體內(nèi)部,引用當(dāng)前環(huán)境的其他變量。所以需要有一種機(jī)制,能夠在函數(shù)體內(nèi)部獲得當(dāng)前的運(yùn)行環(huán)境(context)。所以,this就出現(xiàn)了,它的設(shè)計(jì)目的就是在函數(shù)體內(nèi)部,指代函數(shù)當(dāng)前的運(yùn)行環(huán)境。
this 的用法在理解了 this 的原理之后,我們用下面的 5 種情況,來(lái)討論 this 的用法。
1、純粹的函數(shù)調(diào)用這是函數(shù)的最通常用法,屬于全局性調(diào)用,因此 this 就代表全局對(duì)象 window。
function test(){ this.x = 1; console.log(this.x); } test(); // 12、作為對(duì)象方法的調(diào)用
函數(shù)作為某個(gè)對(duì)象的方法調(diào)用,這時(shí) this 就指這個(gè)上級(jí)對(duì)象。
function test(){ console.log(this.x); } var o = {}; o.x = 1; o.m = test; o.m(); // 13、作為構(gòu)造函數(shù)調(diào)用
所謂構(gòu)造函數(shù),就是通過(guò)這個(gè)函數(shù)生成一個(gè)新對(duì)象(object)。這時(shí),this 就指這個(gè)新對(duì)象。
function test(){ this.x = 1; } var o = new test(); console.log(o.x); // 14、apply調(diào)用
apply() 是函數(shù)對(duì)象的一個(gè)方法,它的作用是改變函數(shù)的調(diào)用對(duì)象,它的第一個(gè)參數(shù)就表示改變后的調(diào)用這個(gè)函數(shù)的對(duì)象。因此,this 指的就是這第一個(gè)參數(shù)。
var x = 0; function test() { console.log(this.x); } var o = {}; o.x = 1; o.m = test; o.m.apply(); //0
apply() 的參數(shù)為空時(shí),默認(rèn)調(diào)用全局對(duì)象。因此,這時(shí)的運(yùn)行結(jié)果為0,證明this指的是全局對(duì)象。
它與上文中提到的 call 的作用是一樣的,只是寫(xiě)法上略有區(qū)別。由于篇幅原因,我會(huì)另啟一篇,來(lái)詳述它們的用法。
5、箭頭函數(shù)ES6 中的箭頭函數(shù),在大部分情況下,使得 this 的指向,變得符合我們的預(yù)期。但有些時(shí)候,它也不是萬(wàn)能的,一不小心的話,this 同樣會(huì)失控。
因?yàn)槠鶅?nèi)容較多,我會(huì)另寫(xiě)一篇文章來(lái)介紹。
另一道面試題最后,讓我們來(lái)鞏固一下 this 的概念和用法。來(lái)看一道面試題:
window.val = 1; var obj = { val: 2, dbl: function () { this.val *= 2; val *= 2; console.log("val:", val); console.log("this.val:", this.val); } }; // 說(shuō)出下面的輸出結(jié)果 obj.dbl(); var func = obj.dbl; func();
答案是輸出:2 、 4 、 8 、 8 。
解析:
執(zhí)行 obj.dbl(); 時(shí), this.val 的 this 指向 obj,而下一行的 val 指向 window。所以,由 window.val 輸出 2,obj.val 輸出 4 。
最后一行 func(); 的調(diào)用者是 window。 所以,現(xiàn)在的 this.val 的 this 指向 window。
別忘了剛才的運(yùn)算結(jié)果,window.val 已經(jīng)是 2 了,所以現(xiàn)在 this.val *= 2; 的執(zhí)行結(jié)果就是 4。
val *= 2; 的執(zhí)行結(jié)果,就是 8 了。 所以,最終的結(jié)果就是輸出 8 和 8 。
總結(jié)this 指代了函數(shù)當(dāng)前的運(yùn)行環(huán)境,依賴于函數(shù)調(diào)用的上下文條件,在運(yùn)行時(shí)才會(huì)進(jìn)行綁定。請(qǐng)牢記總原則:this 總會(huì)指向,調(diào)用函數(shù)的那個(gè)對(duì)象。
參考文獻(xiàn)this 與對(duì)象原型
JavaScript 的 this 原理
PS:歡迎關(guān)注我的公眾號(hào) “超哥前端小?!?,交流更多的想法與技術(shù)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/101252.html
摘要:在這里,如果用箭頭函數(shù),可以這樣改寫(xiě)箭頭函數(shù)并沒(méi)有自己的,所以事件處理函數(shù)的調(diào)用者并不受影響。比如,在需要?jiǎng)討B(tài)上下文的場(chǎng)景中,使用箭頭函數(shù)需要格外地小心,這些場(chǎng)景包括對(duì)象的方法原型方法事件的回調(diào)構(gòu)造函數(shù)。 showImg(https://segmentfault.com/img/bVboce6?w=1304&h=734); 前言 年味兒漸散,收拾下心情,繼續(xù)敲代碼吧。 對(duì)于即將到來(lái)金三...
摘要:原題如下寫(xiě)一個(gè)方法,當(dāng)使用下面的語(yǔ)法調(diào)用時(shí),能正常工作這道題要考察的,就是對(duì)函數(shù)柯里化的理解。當(dāng)參數(shù)只有一個(gè)的時(shí)候,進(jìn)行柯里化的處理。這其實(shí)就是函數(shù)柯里化的簡(jiǎn)單應(yīng)用。 showImg(https://segmentfault.com/img/bVbopGm?w=620&h=350); 前言 這是前端面試題系列的第 6 篇,你可能錯(cuò)過(guò)了前面的篇章,可以在這里找到: ES6 中箭頭函數(shù)的...
摘要:要想注冊(cè)過(guò)的事件能夠被解除,必須將回調(diào)函數(shù)保存起來(lái),否則無(wú)法解除。當(dāng)用阻止瀏覽器的默認(rèn)行為時(shí),會(huì)做下面這件事停止回調(diào)函數(shù)執(zhí)行并立即返回。 showImg(https://segmentfault.com/img/bVboOcb?w=750&h=422); 前言 這是前端面試題系列的第 7 篇,你可能錯(cuò)過(guò)了前面的篇章,可以在這里找到: 理解函數(shù)的柯里化 ES6 中箭頭函數(shù)的用法 thi...
摘要:年求職面經(jīng)及總結(jié)我的求職之路差不多走到盡頭了感覺(jué)真是精疲力盡了把這大半年的經(jīng)歷和面試總結(jié)寫(xiě)下來(lái)希望能給和我一樣在求職路上煎熬的人一點(diǎn)幫助先說(shuō)背景微電子科學(xué)與工程專業(yè)學(xué)過(guò)兩門(mén)和相關(guān)的課程語(yǔ)言和單片機(jī)這個(gè)專業(yè)的唯一好處就是大部分人并不知道這個(gè)專 18年求職面經(jīng)及總結(jié) 我的求職之路差不多走到盡頭了,感覺(jué)真是精疲力盡了.把這大半年的經(jīng)歷和面試總結(jié)寫(xiě)下來(lái),希望能給和我一樣在求職路上煎熬的人一點(diǎn)幫...
摘要:年求職面經(jīng)及總結(jié)我的求職之路差不多走到盡頭了感覺(jué)真是精疲力盡了把這大半年的經(jīng)歷和面試總結(jié)寫(xiě)下來(lái)希望能給和我一樣在求職路上煎熬的人一點(diǎn)幫助先說(shuō)背景微電子科學(xué)與工程專業(yè)學(xué)過(guò)兩門(mén)和相關(guān)的課程語(yǔ)言和單片機(jī)這個(gè)專業(yè)的唯一好處就是大部分人并不知道這個(gè)專 18年求職面經(jīng)及總結(jié) 我的求職之路差不多走到盡頭了,感覺(jué)真是精疲力盡了.把這大半年的經(jīng)歷和面試總結(jié)寫(xiě)下來(lái),希望能給和我一樣在求職路上煎熬的人一點(diǎn)幫...
閱讀 3201·2021-11-23 09:51
閱讀 2046·2021-09-09 09:32
閱讀 1136·2019-08-30 15:53
閱讀 3024·2019-08-30 11:19
閱讀 2542·2019-08-29 14:15
閱讀 1501·2019-08-29 13:52
閱讀 600·2019-08-29 12:46
閱讀 2884·2019-08-26 12:18