摘要:回調(diào)函數(shù)在回調(diào)函數(shù)中的指向也會(huì)發(fā)生變化。在閉包回調(diào)函數(shù)賦值等場(chǎng)景下我們都可以利用來(lái)改變的指向,以達(dá)到我們的預(yù)期。文章參考系列文章理解閉包理解執(zhí)行棧理解作用域理解數(shù)據(jù)類(lèi)型與變量原文發(fā)布在我的公眾號(hào),點(diǎn)擊查看。
這是本系列的第 5 篇文章。
還記得上一篇文章中的閉包嗎?點(diǎn)擊查看文章 理解 JavaScript 閉包 。
在聊 this 之前,先來(lái)復(fù)習(xí)一下閉包:
var name = "Neil"; var person = { name: "Leo", sayHi: function() { return function () { return "Hi! My name is " + this.name; } } }; person.sayHi()(); // "Hi! My name is Neil"
上一篇文章說(shuō),我們可以把閉包簡(jiǎn)單地理解為函數(shù)返回函數(shù)。所以這里的閉包結(jié)構(gòu)是:
// ... function () { return "Hi! My name is " + this.name; } // ...
但是你有沒(méi)有發(fā)現(xiàn),這個(gè)函數(shù)執(zhí)行的結(jié)果是 “Hi! My name is Neil” 。等等,我不是叫 Leo 嗎?怎么給我改了個(gè)名字?!
我一分析,原來(lái)是 this 在其中作祟,且聽(tīng)我慢慢道來(lái)這“改名的由來(lái)”。
§ this 從何而來(lái)首先,你得確保你已經(jīng)清楚執(zhí)行棧與執(zhí)行上下文的知識(shí)。點(diǎn)擊查看文章 理解 JavaScript 執(zhí)行棧 。
ECMAScript 5.1 中定義 this 的值為執(zhí)行上下文中的 ThisBinding。而 ThisBinding 簡(jiǎn)單來(lái)說(shuō)就是由 JS 引擎創(chuàng)建并維護(hù),在執(zhí)行時(shí)被設(shè)置為某個(gè)對(duì)象的引用。
在 JS 中有三種情況可以創(chuàng)建上下文:初始化全局環(huán)境、eval() 和執(zhí)行函數(shù)。
§ 全局中的 thisvar num = 1; function getName () { return "Leo"; } this.num; // 1 this.getName(); // Leo this == window; // true
當(dāng)我們?cè)跒g覽器中運(yùn)行這段代碼,JS 引擎會(huì)將 this 設(shè)置為 window 對(duì)象。而聲明的變量和函數(shù)被作為屬性掛載到 window 對(duì)象上。當(dāng)然,在嚴(yán)格模式下,全局中 this 的值設(shè)置為 undefined。
"use strcit"; var num = 1; function getName () { return "Leo"; } this.num; // TypeError this.getName(); // TypeError this == undefined; // true
開(kāi)啟嚴(yán)格模式后,全局 this 將指向 undefined,所以調(diào)用 this.num 會(huì)報(bào)錯(cuò)。
§ eval() 中的 thiseval() 不被推薦使用,我現(xiàn)在對(duì)其也不太熟悉,這里嘗試著說(shuō)一下。初學(xué)者可以直接跳到下一節(jié)。
結(jié)合所查閱的資料,目前我對(duì) eval() 的理解如下:
eval(...) 直接調(diào)用,被理解為是一個(gè) lvalue,也有說(shuō)是 left unchanged,字面理解為余下不變。什么是“余下不變”?我理解為直接調(diào)用 eval(...),其中代碼的執(zhí)行環(huán)境不變,依舊為當(dāng)前環(huán)境,this 也依舊指向當(dāng)前環(huán)境中的調(diào)用對(duì)象。
而使用類(lèi)似 (1, eval)(...) 的代碼,被稱(chēng)為間接調(diào)用。(1, eval) 是一個(gè)表達(dá)式,你可以這樣認(rèn)為 (true && eval) 或者 (0 : 0 ? eval)。間接調(diào)用的 eval 始終認(rèn)為其中的代碼執(zhí)行在全局環(huán)境,將 this 綁定到全局對(duì)象。
var x = "outer"; (function() { var x = "inner"; // "direct call: inner" eval("console.log("direct call: " + x)"); // "indirect call: outer" (1, eval)("console.log("indirect call: " + x)"); })();
關(guān)于 eval(),現(xiàn)在不敢確定,如有錯(cuò)誤,歡迎指正。
§ 函數(shù)中的 this ◆ 一般情況首先,我們需要明確的是,在 JS 中函數(shù)也屬于對(duì)象,它可以擁有屬性,this 就是函數(shù)在執(zhí)行時(shí)獲得的屬性。一般情況下,在全局環(huán)境中直接調(diào)用函數(shù),函數(shù)中的 this 會(huì)在調(diào)用時(shí)被 JS 引擎設(shè)置為全局對(duì)象 window(同樣在嚴(yán)格模式下為 undefined)。
var name = "Leo"; function getName() { var name = "Neil"; console.log(this); // [object Window] return this.name; } getName(); // Leo◆ 作為對(duì)象的方法
函數(shù)可以作為對(duì)象的方法被該對(duì)象調(diào)用,那么這種情況 this 會(huì)被設(shè)置為該對(duì)象。
var name = "Neil"; var person = { name: "Leo", sayHi: function() { console.log(this); // person return "Hi! My name is " + this.name; } }; person.sayHi(); // "Hi! My name is Leo"
當(dāng) person 對(duì)象調(diào)用 sayHi() 方法時(shí),this 被指向 person。
◆ 特殊的內(nèi)置函數(shù)JS 還提供了一種供開(kāi)發(fā)者自定義 this 的方式,它提供了 3 種方式。
Function.prototype.call(thisArg, argArray)
Function.prototype.apply(thisArg [, arg1 [, args2, ...]])
Function.prototype.bind(thisArg [, arg1 [, args2, ...]])
我們可以通過(guò)設(shè)置 thisArg 的值,來(lái)自定義函數(shù)中 this 的指向。
var leo = { name: "Leo", sayHi: function () { return "Hi! My name is " + this.name; } } var neil = { name: "Neil" }; leo.sayHi(); // "Hi! My name is Leo" ?leo.sayHi.call(neil); // "Hi! My name is Neil"
這里,我們通過(guò) call() 將 sayHi() 中 this 的指向綁定為 neil 對(duì)象,從而取代了默認(rèn) 的 this 指向 leo 對(duì)象。
關(guān)于函數(shù)的 call(), apply(), bind() 我將在后面另寫(xiě)一篇文章,敬請(qǐng)期待。
§ this 引起的令人費(fèi)解的現(xiàn)象 ◆ 閉包通過(guò)前面的介紹,我想你對(duì) this 已經(jīng)有了初步的印象。那么,回到文章開(kāi)頭的問(wèn)題,this 是怎么改變了我的名字?換句話說(shuō),this 在閉包的影響下指向發(fā)生了怎樣的變動(dòng)?
再看一下代碼:
var name = "Neil"; var person = { name: "Leo", sayHi: function() { return function () { return "Hi! My name is " + this.name; } } }; person.sayHi()(); // "Hi! My name is Neil"
通過(guò)上一篇文章 理解 JavaScript 閉包,函數(shù)返回函數(shù)會(huì)形成閉包。在這種情況下,閉包往往所執(zhí)行的環(huán)境與所定義的環(huán)境不一致,而 this 的值卻是在執(zhí)行時(shí)決定的。所以,當(dāng)上面代碼中的閉包在執(zhí)行時(shí),它所在的執(zhí)行上下文是全局環(huán)境,this 將被設(shè)置為 window(嚴(yán)格模式下為 undefined)。
怎么解決?我們可以利用 call / apply / bind 來(lái)修改 this 的指向。
var name = "Neil"; var person = { name: "Leo", sayHi: function() { return function () { return "Hi! My name is " + this.name; } } }; person.sayHi().call(person); // "Hi! My name is Leo"
這里利用 call() 將 this 指向 person。OK,我的名字回來(lái)了,“Hi! My name is Leo” ^^
當(dāng)然,我們還有第二種解決方法,閉包的問(wèn)題就讓閉包自己解決。
var name = "Neil"; var person = { name: "Leo", sayHi: function() { var that = this; // 定義一個(gè)局部變量 that return function () { return "Hi! My name is " + that.name; // 在閉包中使用 that } } }; person.sayHi()(); // "Hi! My name is Leo"
在 sayHi() 方法中定義一個(gè)局部變量,閉包可以將這個(gè)局部變量保存在內(nèi)存中,從而解決問(wèn)題。
◆ 回調(diào)函數(shù)在回調(diào)函數(shù)中 this 的指向也會(huì)發(fā)生變化。
var name = "Neil"; var person = { name: "Leo", sayHi: function() { return "Hi! My name is " + this.name; } }; var btn = document.querySelector("#btn"); btn.addEventListener("click", person.sayHi); // "Hi! My name is undefined"
這里 this 既不指向 person,也不指向 window。那它指向什么?
btn 對(duì)象,它是一個(gè) DOM 對(duì)象,有一個(gè) onclick 方法,在這里定義為 person.sayHi。
{ // ... onclick: person.sayHi // ... }
所以,當(dāng)我們執(zhí)行上面的代碼,this.name 的值為 undefined,因?yàn)?btn 對(duì)象上沒(méi)有定義 name 屬性。我們給 btn 對(duì)象自定義一個(gè) name 屬性來(lái)驗(yàn)證一下。
var btn = document.querySelector("#btn"); btn.name = "Jackson"; btn.addEventListener("click", person.sayHi); // "Hi! My name is Jackson"
原因說(shuō)清楚了,解決方案同樣可用過(guò) call / apply / bind 來(lái)改變 this 的指向,使其綁定到 person 對(duì)象。
btn.addEventListener("click", person.sayHi.bind(person)); // "Hi! My name is Leo"◆ 賦值
var name = "Neil"; var person = { name: "Leo", sayHi: function() { return "Hi! My name is " + this.name; } }; person.sayHi(); // "Hi! My name is Leo" var foo = person.sayHi; foo(); // "Hi! My name is Neil"
當(dāng)把 person.sayHi() 賦值給一個(gè)變量,這個(gè)時(shí)候 this 的指向又發(fā)生了變化。因?yàn)?foo 執(zhí)行時(shí)是在全局環(huán)境中,所以 this 指向 window(嚴(yán)格模式下指向 undefined)。
同樣,我們可以通過(guò) call / apply / bind 來(lái)解決,這里就不貼代碼了。
§ 別忘了 new在 JS 中,我們聲明一個(gè)類(lèi),然后 new 一個(gè)實(shí)例。
function Person(name) { this.name = name; } var her = Person("Angelia"); console.log(her.name); // TypeError var me = new Person("Leo"); console.log(me.name); // "Leo"
如果我們直接把調(diào)用這個(gè)函數(shù),this 將指向全局對(duì)象,Person 在這里就是一個(gè)普通函數(shù),沒(méi)有返回值,默認(rèn) undefined,而嘗試訪問(wèn) undefined 的屬性就會(huì)報(bào)錯(cuò)。
如果我們使用 new 操作符,那么 new 其實(shí)會(huì)生成一個(gè)新的對(duì)象,并將 this 指向這個(gè)新的對(duì)象,然后將其返回,所以 me.name 能打印出 “Leo”。
關(guān)于 new 的原理,我會(huì)在后面的文章分享,敬請(qǐng)期待。
§ 小結(jié)你看,this 是不是千變?nèi)f化。但是我們得以不變應(yīng)萬(wàn)變。
在這么多場(chǎng)景下,this 的指向萬(wàn)變不離其宗:它一定是在執(zhí)行時(shí)決定的,指向調(diào)用函數(shù)的對(duì)象。在閉包、回調(diào)函數(shù)、賦值等場(chǎng)景下我們都可以利用 call / apply / bind 來(lái)改變 this 的指向,以達(dá)到我們的預(yù)期。
接下來(lái),請(qǐng)期待文章《理解 JavaScript call/apply/bind》。
◆ 文章參考Understand JavaScript"s "this" With Clarity, and Matser It | Richard Bovell
How does the "this" keyword work | Stack Overflow
ECMAScript Language Specification - $11.1.1 The This Keyword
(1, eval)("this") vs eval("this") in JavaScript | Stack Overflow
§ JavaScript 系列文章理解 JavaScript 閉包
理解 JavaScript 執(zhí)行棧
理解 JavaScript 作用域
理解 JavaScript 數(shù)據(jù)類(lèi)型與變量
Be Good. Sleep Well. And Enjoy.
原文發(fā)布在我的公眾號(hào) camerae,點(diǎn)擊查看。
前端技術(shù) | 個(gè)人成長(zhǎng)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/100207.html
摘要:匿名函數(shù)的執(zhí)行環(huán)境具有全局性,因此它的對(duì)象通常指向。如果對(duì)此有疑惑,可以看知乎上的答案知乎匿名函數(shù)的指向?yàn)槭裁词亲鳛閷?duì)象方法的調(diào)用,指向該對(duì)象當(dāng)函數(shù)作為某個(gè)對(duì)象的方法調(diào)用時(shí),就指這個(gè)函數(shù)所在的對(duì)象。 因?yàn)槿粘9ぷ髦薪?jīng)常使用到this,而且在JavaScript中this的指向問(wèn)題也很容易讓人混淆一部分知識(shí)。 這段時(shí)間翻閱了一些書(shū)籍也查閱了網(wǎng)上一些資料然后結(jié)合自己的經(jīng)驗(yàn),為了能讓自...
摘要:第四點(diǎn)也要著重講下,記住構(gòu)造函數(shù)被操作,要讓正常作用最好不能在構(gòu)造函數(shù)里 4) this、new、call和apply的相關(guān)問(wèn)題 講解this指針的原理是個(gè)很復(fù)雜的問(wèn)題,如果我們從javascript里this的實(shí)現(xiàn)機(jī)制來(lái)說(shuō)明this,很多朋友可能會(huì)越來(lái)越糊涂,因此本篇打算換一個(gè)思路從應(yīng)用的角度來(lái)講解this指針,從這個(gè)角度理解this指針更加有現(xiàn)實(shí)意義。 下面我們看看在ja...
摘要:使用來(lái)調(diào)用函數(shù),會(huì)自動(dòng)執(zhí)行下面操作創(chuàng)建一個(gè)全新的對(duì)象。所以如果是一個(gè)函數(shù)的話,會(huì)是這樣子的創(chuàng)建一個(gè)新對(duì)象連接新對(duì)象與函數(shù)的原型執(zhí)行函數(shù),改變指向新的對(duì)象所以,在使用來(lái)調(diào)用函數(shù)時(shí)候,我們會(huì)構(gòu)造一個(gè)新對(duì)象并把它綁定到函數(shù)調(diào)用中的上。 歡迎來(lái)我的博客閱讀:《加深對(duì) JavaScript This 的理解》 我相信你已經(jīng)看過(guò)很多關(guān)于 JavaScript 的 this 的談?wù)摿?,既然你點(diǎn)進(jìn)來(lái)...
摘要:理解文章中已經(jīng)比較全面的分析了在中的指向問(wèn)題,用一句話來(lái)總結(jié)就是的指向一定是在執(zhí)行時(shí)決定的,指向被調(diào)用函數(shù)的對(duì)象。與和直接執(zhí)行原函數(shù)不同的是,返回的是一個(gè)新函數(shù)。這個(gè)新函數(shù)包裹了原函數(shù),并且綁定了的指向?yàn)閭魅氲摹? 理解 JavaScript this 文章中已經(jīng)比較全面的分析了 this 在 JavaScript 中的指向問(wèn)題,用一句話來(lái)總結(jié)就是:this 的指向一定是在執(zhí)行時(shí)決定的,...
摘要:的關(guān)鍵字總是讓人捉摸不透,關(guān)鍵字代表函數(shù)運(yùn)行時(shí),自動(dòng)生成的一個(gè)內(nèi)部對(duì)象,只能在函數(shù)內(nèi)部使用,因?yàn)楹瘮?shù)的調(diào)用場(chǎng)景不同,的指向也不同。其實(shí)只要理解語(yǔ)言的特性就很好理解。個(gè)人對(duì)中的關(guān)鍵字的理解如上,如有不正,望指正,謝謝。 javascript的this關(guān)鍵字總是讓人捉摸不透,this關(guān)鍵字代表函數(shù)運(yùn)行時(shí),自動(dòng)生成的一個(gè)內(nèi)部對(duì)象,只能在函數(shù)內(nèi)部使用,因?yàn)楹瘮?shù)的調(diào)用場(chǎng)景不同,this的指向也不...
閱讀 2894·2021-11-16 11:44
閱讀 1045·2021-10-09 09:58
閱讀 4587·2021-09-24 09:48
閱讀 4646·2021-09-23 11:56
閱讀 2469·2021-09-22 15:48
閱讀 1977·2021-09-07 10:07
閱讀 3254·2021-08-31 09:46
閱讀 573·2019-08-30 15:56