換個(gè)思路理解Javascript中的this
在網(wǎng)上很多文章都對(duì) Javascript 中的 this 做了詳細(xì)的介紹,但大多是介紹各個(gè)綁定方式或調(diào)用方式下 this 的指向,于是我想有一個(gè)統(tǒng)一的思路來(lái)更好理解 this 指向,使大家更好判斷,以下有部分內(nèi)容不是原理,而是一種解題思路。
從call方法開(kāi)始
call 方法允許切換函數(shù)執(zhí)行的上下文環(huán)境(context),即 this 綁定的對(duì)象。
大多數(shù)介紹 this 的文章中都會(huì)把 call 方法放到***介紹,但此文我們要把 call 方法放在***位介紹,并從 call 方法切入來(lái)研究 this ,因?yàn)?call 函數(shù)是顯式綁定 this 的指向,我們來(lái)看看它如何模擬實(shí)現(xiàn)(不考慮傳入 null 、 undefined 和原始值):
- Function.prototype.call = function(thisArg) {
- var context = thisArg;
- var arr = [];
- var result;
- context.fn = this;
- for (let i = 1, len = arguments.length; i < len; i++) {
- arr.push('arguments[' + i + ']');
- }
- result = eval("context.fn(" + arr + ")");
- delete context.fn;
- return result;
- }
從以上代碼我們可以看到,把調(diào)用 call 方法的函數(shù)作為***個(gè)參數(shù)對(duì)象的方法,此時(shí)相當(dāng)于把***個(gè)參數(shù)對(duì)象作為函數(shù)執(zhí)行的上下文環(huán)境,而 this 是指向函數(shù)執(zhí)行的上下文環(huán)境的,因此 this 就指向了***個(gè)參數(shù)對(duì)象,實(shí)現(xiàn)了 call 方法切換函數(shù)執(zhí)行上下文環(huán)境的功能。
對(duì)象方法中的this
在模擬 call 方法的時(shí)候,我們使用了對(duì)象方法來(lái)改變 this 的指向。調(diào)用對(duì)象中的方法時(shí),會(huì)把對(duì)象作為方法的上下文環(huán)境來(lái)調(diào)用。
既然 this 是指向執(zhí)行函數(shù)的上下文環(huán)境的,那我們先來(lái)研究一下調(diào)用函數(shù)時(shí)的執(zhí)行上下文情況。
下面我門(mén)來(lái)看看調(diào)用對(duì)象方法時(shí)執(zhí)行上下文是如何的:
- var foo = {
- x : 1,
- getX: function(){
- console.log(this.x);
- }
- }
- foo.getX();
從上圖中,我們可以看出getX方法的調(diào)用者的上下文是foo,因此getX方法中的 this 指向調(diào)用者上下文foo,轉(zhuǎn)換成 call 方法為foo.getX.call(foo)。
下面我們把其他函數(shù)的調(diào)用方式都按調(diào)用對(duì)象方法的思路來(lái)轉(zhuǎn)換。
構(gòu)造函數(shù)中的this
- function Foo(){
- this.x = 1;
- this.getX = function(){
- console.log(this.x);
- }
- }
- var foo = new Foo();
- foo.getX();
執(zhí)行 new 如果不考慮原型鏈,只考慮上下文的切換,就相當(dāng)于先創(chuàng)建一個(gè)空的對(duì)象,然后把這個(gè)空的對(duì)象作為構(gòu)造函數(shù)的上下文,再去執(zhí)行構(gòu)造函數(shù),***返回這個(gè)對(duì)象。
- var newMethod = function(func){
- var context = {};
- func.call(context);
- return context;
- }
- function Foo(){
- this.x = 1;
- this.getX = function(){
- console.log(this.x);
- }
- }
- var foo = newMethod(Foo);
- foo.getX();
DOM事件處理函數(shù)中的this
- DOMElement.addEventListener('click', function(){
- console.log(this);
- });
把函數(shù)綁定到DOM事件時(shí),可以當(dāng)作在DOM上增加一個(gè)函數(shù)方法,當(dāng)觸發(fā)這個(gè)事件時(shí)調(diào)用DOM上對(duì)應(yīng)的事件方法。
- DOMElement.clickHandle = function(){
- console.log(this);
- }
- DOMElement.clickHandle();
普通函數(shù)中的this
- var x = 1;
- function getX(){
- console.log(this.x);
- }
- getX();
這種情況下,我們創(chuàng)建一個(gè)虛擬上下文對(duì)象,然后普通函數(shù)作為這個(gè)虛擬上下文對(duì)象的方法調(diào)用,此時(shí)普通函數(shù)中的this就指向了這個(gè)虛擬上下文。
那這個(gè)虛擬上下文是什么呢?在非嚴(yán)格模式下是全局上下文,瀏覽器里是 window ,NodeJs里是 Global ;在嚴(yán)格模式下是 undefined 。
- var x = 1;
- function getX(){
- console.log(this.x);
- }
- [viturl context].getX = getX;
- [viturl context].getX();
閉包中的this
- var x = 1;
- var foo = {
- x: 2,
- y: 3,
- getXY: function(){
- (function(){
- console.log("x:" + this.x);
- console.log("y:" + this.y);
- })();
- }
- }
- foo.getXY();
這段代碼的上下文如下圖:
這里需要注意的是,我們?cè)傺芯亢瘮?shù)中的 this 指向時(shí),只需要關(guān)注 this 所在的函數(shù)是如何調(diào)用的, this 所在函數(shù)外的函數(shù)調(diào)用都是浮云,是不需要關(guān)注的。因此在所有的圖示中,我們只需要關(guān)注紅色框中的內(nèi)容。
因此這段代碼我們關(guān)注的部分只有:
- (function(){
- console.log(this.x);
- })();
與普通函數(shù)調(diào)用一樣,創(chuàng)建一個(gè)虛擬上下文對(duì)象,然后普通函數(shù)作為這個(gè)虛擬上下文對(duì)象的方法立即調(diào)用,匿名函數(shù)中的 this 也就指向了這個(gè)虛擬上下文。
參數(shù)中的this
- var x = 1;
- var foo = {
- x: 2,
- getX: function(){
- console.log(this.x);
- }
- }
- setTimeout(foo.getX, 1000);
函數(shù)參數(shù)是值傳遞的,因此上面代碼等同于以下代碼:
- var getX = function(){
- console.log(this.x);
- };
- setTimeout(getX, 1000);
然后我們又回到了普通函數(shù)調(diào)用的問(wèn)題。
全局中的this
全局中的 this 指向全局的上下文
- var x = 1;
- console.log(this.x);
復(fù)雜情況下的this
- var x = 1;
- var a = {
- x: 2,
- b: function(){
- return function(){
- return function foo(){
- console.log(this.x);
- }
- }
- }
- };
- (function(){
- var x = 3;
- a.b()()();
- })();
看到上面的情況是有很多個(gè)函數(shù),但我們只需要關(guān)注 this 所在函數(shù)的調(diào)用方式,首先我們來(lái)簡(jiǎn)化一下如下:
- var x = 1;
- (function(){
- var x = 3;
- var foo = function(){
- console.log(this.x);
- }
- foo();
- });
this 所在的函數(shù) foo 是個(gè)普通函數(shù),我們創(chuàng)建一個(gè)虛擬上下文對(duì)象,然后普通函數(shù)作為這個(gè)虛擬上下文對(duì)象的方法立即調(diào)用。因此這個(gè) this指向了這個(gè)虛擬上下文。在非嚴(yán)格模式下是全局上下文,瀏覽器里是 window ,NodeJs里是 Global ;在嚴(yán)格模式下是 undefined 。
總結(jié)
在需要判斷 this 的指向時(shí),我們可以安裝這種思路來(lái)理解:
- 判斷 this 在全局中OR函數(shù)中,若在全局中則 this 指向全局,若在函數(shù)中則只關(guān)注這個(gè)函數(shù)并繼續(xù)判斷。
- 判斷 this 所在函數(shù)是否作為對(duì)象方法調(diào)用,若是則 this 指向這個(gè)對(duì)象,否則繼續(xù)操作。
- 創(chuàng)建一個(gè)虛擬上下文,并把this所在函數(shù)作為這個(gè)虛擬上下文的方法,此時(shí) this 指向這個(gè)虛擬上下文。
- 在非嚴(yán)格模式下虛擬上下文是全局上下文,瀏覽器里是 window ,Node.js里是 Global ;在嚴(yán)格模式下是 undefined 。
圖示如下: