深刻理解JS函數中this指針的指向

函數在執行時,會在函數體內部自動生成一個this指針。誰直接調用產生這個this指針的函數,this就指向誰。es6

怎麼理解指向呢,我認爲指向就是等於。例如直接在js中輸入下面的等式:數組

console.log(this===window);//true

 狀況不一樣,this指向的對象也不一樣。例如:瀏覽器

1.  函數聲明的狀況app

var bj=10;
function add(){
    var bj=20;
    console.log(this);//window
    console.log(this.bj);//10
    console.log(bj);//20
    console.log(this.bj+bj);//30
}
add();
window.add();

(1) 執行了add()以後,此時的this指向的是window對象,爲何呢?由於這時候add是全局函數,是經過window直接調用的。因此下面我專門寫了個window.add()就是爲了說明,全局函數的this都是指向的window。函數

(2) 就像alert()自帶的警告彈窗同樣,window.alert()執行以後也是同樣的效果。因此只要是   window點   這種調用方式均可以省略掉,所以警告彈窗能夠直接使用alert()。this

2.  函數表達式spa

var bj=10;
var zjj=function(){
    var bj=30;
    console.log(this);//window
    console.log(this.bj);//10
    console.log(bj);//30
    console.log(this.bj+bj);//40
}
console.log(typeof zjj);//function
zjj();
window.zjj();

(1) 執行了zjj()以後,函數中的this也是指向window對象。緣由和第一個是同樣的,都是經過window這個對象直接調用prototype

 

3.  函數做爲對象的屬性去調用------例一線程

var bj=10;
var obj={
    name:"八戒",
    age:"500",
    say:function(){
        var bj=40;
        console.log(this);//就是obj這個對象
        console.log(this.bj);//undefined
        console.log(this.name);//八戒
    }
}
obj.say();
window.obj.say();    

(1) 當obj.say()被執行的時候,此時的this指向的是 obj 這個對象,爲何呢?由於say函數是經過obj這個對象直接調用的。指針

(2) 那有人可能會問了,obj對象實際上也是經過window對象調用的,爲何this不指向window呢?我認爲是由於say這個函數是經過 obj 對象直接調用的,而沒有經過 window 對象直接調用,所以this不會指向window。看下面的例子就明白了。

 

3.1  函數做爲對象的屬性去調用------例二

var bj=10;
var obj={
    name:"八戒",
    age:500,
    say:function(){
        console.log(this);//是obj這個對象
        console.log(this.bj);//undefined
        console.log(this.name)//八戒
    },
    action:{
        name:"悟空",
        age:1000,
        say:function(){
            console.log(this);//是action這個對象
            console.log(this.bj);//undefined
            console.log(this.name)//悟空
        }
    }
}
obj.say();
obj.action.say();
window.obj.action.say();

(1) obj.say()執行以後,此時這個函數裏的this指向的是obj對象,緣由是由於say函數是經過obj直接調用的。

(2) obj.action.say()執行以後,此時這個函數裏的this指向的是action對象,緣由是由於say函數是經過action對象直接調用的。並無經過obj直接調用。也沒有經過 window 直接調用,因此此時action對象中say函數裏的的this指向並不會是obj或者window。

 

3.2  函數做爲對象的屬性去調用------例三

var bj=10;
var obj={
    name:"八戒",
    age:500,
    say:function(){
        console.log(this);//就是obj這個對象
        console.log(this.bj);//undefined
        console.log(this.name)//八戒
        function wk(){
            console.log(this);//window
            console.log(this.bj);//10
            console.log(this.name);//這裏顯示的是爲空
        }
        wk();        
    },
}
obj.say();

(1) 這種狀況下,say函數裏的this指針仍是指向的obj,緣由是由於say函數是經過obj直接調用

(2) 可是這時候wk函數中的this就是指向的是window了。爲何呢?由於 wk()函數在 say()函數中,是屬於普通函數調用,可是並無經過say或者obj直接調用,只是自執行,這個時候,wk就是一個全局函數,所以該函數的this指向的就是window。

(3) 那爲何this.name是顯示的爲空呢?由於 window 對象中自己就有一個 name 值,並非某處添加的,若是把name換成age,獲得的就是undefined了。

(4) 那怎樣讓wk()函數中的this指向obj呢。一種方式就是在say函數中把say()函數的this用變量保存起來,即 var that=this;  而後wk()函數使用that就能達到指向obj的目的了。另外的方式是經過apply或者call來改變。

(5) 那wk()在這裏能不能寫成window.wk()呢?這樣是不行的,會報錯,window.wk is not a function。爲何不行呢,this不是指向window嗎,爲何widow對象裏滅有wk()這個函數。。這個嘛,我也不知道,先留個坑,後面再來填 ×××

 

3.3  函數做爲對象的屬性去調用------例四

var bj=10;
var obj={
    name:"八戒",
    age:"500",
    say:function(){
        var bj=40;
        console.log(this);//window
        console.log(this.bj);//10
        console.log(this.name);//這裏沒有輸出內容
    }
}
var elseObj=obj.say;
elseObj();

 (1) 執行了elseObj()函數以後,爲何say函數中的this卻指向了window呢?首先要理解這句話:誰直接調用產生這個this指針的函數,this就指向誰。當obj.say賦值給elseObj的時候,elseObj只是一個函數,而並無執行,所以this指針的指向並不明確,這個時候執行到 var elseObj=obj.say的 時候,整程序至關於:

var bj=10;
var elseObj=function(){
    var bj=40;
    console.log(this);
    console.log(this.bj);
    console.log(this.name);
}
elseObj();

     這就和 第2種 函數表達式的狀況同樣了。因此,當執行elseObj()的時候,this就指向window,this.obj爲10,由於這時候elseObj()是經過 window 直接調用

(2) this.name爲空是由於 window 對象中自己就有一個 name 值,並非某處添加的,若是把name換成其它的好比age,獲得的就是undefined了,由於全局並無age屬性。

 

3.4  函數做爲對象的屬性去調用------例五

var bj=10;
var obj={
    name:"八戒",
    age:500,
    say:function(){
        return function(){
            console.log(this);//window
            console.log(this.bj);//10
            console.log(this.age);//undefined
        }
    }
}
obj.say()();
//    var elseObj=obj.say();
//    elseObj();

(1) obj.say()()爲何會有兩個括號?由於obj.say()執行以後返回的是一個函數,並無執行,再加一個括號就是執行返回的那個匿名函數。

(2) 若是不習慣也可使用上面註釋的那種方式,是同樣的效果。

(3) 執行了函數以後,爲何返回的函數中this是指向window的呢?那是由於執行obj.say()的時候,只是一個函數,至關於就是註釋裏的第一行代碼,這時候返回的函數並未被執行。當再加一個括號的時候,就是執行了返回的那個函數,這個時候返回的函數就至關因而一個全局函數,是經過window直接調用,所以this就是指向的是window。

 

4.  工廠模式中this的指向------例一

var bj=10;
function fun(a,b){
   console.log(this);//window對象
var bj=20; var sun=new Object(); sun.one=a; sun.two=b; sun.say=function(){ console.log(this);//是sun對象,{one: 2, two: 3, say: ƒ()} console.log(this.bj);//undefined console.log(this.one);//2 } return sun; } var wk=fun(2,3); wk.say();

 (1) 話說爲何叫工廠模式,我搞不太清楚,不過這個不重要,重要的是經過這個模式,在每次調用函數的時候,雖然每次都返回的是sun這個對象,可是每一個對象都是不類似的,即便內容同樣,好比 var sf=fun(2,3); console.log(sf===wk);//false 。

(2) 那爲何say()函數執行以後,this是指向返回的那個對象呢?這個很明顯嘛,say()是經過wk這個對象直接調用的,而wk是fun函數返回sun對象。因此這裏的this就指向的是返回的對象。因此this.bj爲undefined,由於返回的對象中沒有bj屬性。

(3) 我認爲這種模式最重要的仍是 renturn sun這個返回語句,這個是必不可少的。

(4) fun(a,b)這個函數中的this指向的是window,緣由是執行 var wk=fun(2,3); 的時候,fun函數已經被執行了,而且直接調用它的就是window,因此這時的this是指向的window。

 

4.1  工廠模式中this的指向------例二

var bj=10;
function fun(a,b){
   console.log(this);//window對象
var bj=20; var sun=new Object(); sun.one=a; sun.two=b; sun.say=function(){ console.log(this);//是sun對象,{one: 2, two: 3, say: ƒ()} return function(){ console.log(this);//是window對象 } } return sun; } var wk=fun(2,3); var ss=wk.say(); ss();

 (1) 爲何say函數中return 的函數中this是指向的window對象呢?首先,執行到 var wk=fun(2,3); 的時候,wk是一個對象。繼續執行下一句代碼,ss這時候是一個函數,就是經過say函數返回以後賦值的。這時候返回的函數還未執行,this指向並不明確。當執行到最後一句代碼,ss()函數執行了。這時候,ss函數就是一個全局函數,是經過window直接調用的。因此這時的this指向的是window。

(2) 若是say中返回的是一個對象,對象中又有個函數,像下面同樣:

sun.say=function(){
    console.log(this);//是sun對象,{one: 2, two: 3, say: ƒ()}
    return {
        wk:"1",
        say:function(){
            console.log(this);
        }
    }
}

   這時候執行到ss.say()的時候,this指向的就是ss這個對象,即經過say函數返回的那個對象。緣由仍是同樣,say函數是經過ss直接調用的,而ss對象是wk.say()返回的對象。

 

5.  構造函數中this的指向

var bj=10;
function Add(){
    var bj=20;
    this.bj=30;
    this.say=function(){
        console.log(this);//Add {bj: 30, say: ƒ()}
        console.log(this.bj);//30
    }
     console.log(this) ;//Add {bj: 30, say: ƒ()}
}
var obj=new Add();
console.log(typeof obj);//object
obj.say();

 (1) 要明白構造函數的this指向,咱們須要明白調用構造函數經歷的步驟:

  a。建立一個新對象。

  b。將構造函數的做用域賦給新對象(所以this就指向了這個新對象)。

  c。執行構造函數中的代碼(爲這個新對象添加屬性)。

  d。返回新對象。

  摘至js高程 6.2.2節。

(2) 構造函數與工廠模式相比:(原諒照搬js高程的話)。

  a。沒有顯示的建立對象。

  b。沒有return語句。

  c。直接將屬性和方法賦值給 this 對象。

  摘至js高程 6.2.2節。

(3)  首先,obj.say()執行以後,say函數中this的指向是obj對象,這個很明顯,再也不贅述。在不用new操做符的時候,Add()函數裏的this指向的就是window;可是使用了new操做符以後,Add()函數中 console.log(this) 這個this爲何是obj對象,而不是window呢?

(4)  這個緣由我認爲在js權威指南4.6節對象建立表達式和8.2.3構造函數使用中,有所說明。使用new操做符的時候,js先建立一個新的空對象,而後,js傳入指定的參數並將這個新對象當作this的值來調用一個指定的函數。這個函數可使用this來初始化這個新建立對象的屬性。因此當使用new操做符以後,函數中的this指向的是新建立的對象。因此構造函數中的this就是指向new出來的那個對象。

(5)  若是構造函數中有return語句,那麼此時 var obj=new Add(); obj就是return出來的內容,可是Add函數中的this仍是指向的建立的新對象Add;

 

6. 原型對象中this的指向

var bj=10;
function Add(){
  console.log(this);//Add{}
}; Add.prototype.bj
=10; Add.prototype.say=function(){ console.log(this);//Add{} return function(){ console.log(this);//window } } var obj=new Add;//沒傳參數能夠省略括號 obj.say()();

 (1)  obj.say()()執行的時候,this指向的是window,這個仍是由於obj.say()執行時返回的是一個函數,而後再加一個括號,就執行返回的這個函數,此時這個函數屬於全局函數,因此,this會指向window

(2)  Add()這個構造函數中的this指向的是Add{},緣由和上面構造函數中this的指向同樣。

(3)  Add.prototype.say=function(){ console.log(this) }  這裏面的this 也是指向的是Add{},至於緣由,我認爲是由於say()這個函數是經過obj直接調用的,因此this指向的是obj,因此是Add{}。

 

總結:

  要想判斷函數中this的指向,只要知道誰直接調用產生this指針的函數,this就指向誰了。只是要注意使用了new 操做符以後,構造函數內部的this指向的是新對象,通俗點講就是new出來的新實例。

 


  

更新: 

再來一個例子說明一下,通俗的理解一下this指針的指向:誰直接調用產生 this 指針的函數,這函數裏的 this 指針就指向誰。

var factory = function(){
  this.a = 'first-A';  
  this.b = 'first-B';
  this.c = {
    a:'second-A',
    b:function(){
        console.log(this.a)
        return this.a
    }
  }    
}
new factory().c.b(); // second-A

(1) 這個代碼首先考的是運算符的優先級。MDN運算符優先級   能夠先查看優先級再思考。

(2) new的優先級和 點運算符等級同樣,從左至右執行,因此先執行 new factory() 而後在執行 點運算符。

(3) 執行了 new 操做以後。而後發現函數調用的優先級和成員訪問運算符的優先級同樣,而後遵循從左到有的執行方式。所以就先執行 成員訪問運算符 .c

(4) 這時 .c 就是一個對象,而後再取 b 屬性,是個函數。這個時候 this 指針已經產生, 而產生這個this指針的是b函數,並且是經過 c 調用的。所以此時 this 的指向就是 c 對象。因此最後打印出second-A

(5) 若是想要 c 裏面的 b函數中 this指向的是 factory 實例。要麼使用 bind.apply,call等方法來強行改變。 要麼就把 b 函數寫成 es6箭頭函數的方式。這樣 b 函數就沒有this指針,而 b 函數裏面的this,就是上一級的 this。

 

而後再來講一下回調函數當即執行函數(IIFE),點擊事件 的 this 的指向。

弄清楚這個以前,咱們要知道,函數傳參是按值傳遞若是是基本數據類型,則是直接複製數據的值傳過去。若是是引用類型,好比對象,函數這種,傳遞的就是該數據 在堆中存放的地址

舉個例子來證實: 

var obj = {
    a: 10,
    getA: function(){
        console.log(this.a);
    }
}
active(obj.getA);
function active(fn) {
    console.log(fn === obj.getA ) // true
    fn();
}

(1) 形參 fn 與 obj.getA 全等,就說明了 傳的是存放的地址,而不是內容。由於對象就算內容相同,存放地址不一樣的話,也不會相等。

 

那麼,回調函數就是傳的 函數在堆中的地址,也就是說,回調函數中 this 的指向,決定於執行回調函數 時的執行上下文環境。

首先是 setTimeout,setInterval 這種類型的回調函數。

setTimeout的回調 --- 例一

setTimeout(function(){
   console.log(this) 
})

(1) 這是最最經常使用的常見的定時器用法,回調函數裏的this指向的是window。

(2) 由setTimeout()調用的代碼運行在與所在函數徹底分離的執行環境上。這會致使,這些代碼中包含的 this 關鍵字在非嚴格模式會指向 window (或全局)對象,嚴格模式下爲 undefined,這和所指望的this的值是不同的。在嚴格模式下,setTimeout( )的回調函數裏面的this仍然默認指向window對象, 並非undefined。 這幾句話是 MDN上,setTimeout中 關於 this 的問題  裏對 this 指向的解釋。

(3) 個人理解是:因爲setTimeout屬於宏任務,它的回調在延時以後才進入到主線程執行,而函數執行的時候才明確 this 的指向。執行的時候,因爲沒有設置內部this的指向。至關因而普通函數調用。因此會默認指向window

 

setTimeout的回調 --- 例二

var obj = {
    age:10,
    getage:function(){
        console.log(this.age)
    }
}

setTimeout(obj.getage,1000)   // undefined

setTimeout(function(){
    obj.getage()  // 10
},1000)

(1) 第一個setTimeout,執行obj.getage 以後,至關於setTimeout的回調是一個匿名函數,執行的時候,函數內部未設置this的指向。至關因而普通函數調用。因此this默認指向window,因此結果是undefined。

(2) 第二個setTimeout,傳給setTimeout的也是一個匿名回調函數,執行匿名函數,執行到 obj.getage() 的時候,getage函數裏的this,指向的就是obj了,因此能打印出10。仍是遵循 誰調用產生 this指針的函數,this就指向誰的規則

 

對於 數組的遍歷方法:

foreach,map,filter,some,每次 callback 函數被調用的時候,this 都會指向 最後一個參數 thisArg 的這個對象。若是省略了 thisArg 參數,或者賦值爲 null 或 undefined,則 this 指向全局對象 。在嚴格模式下則是undefined(未傳值的時候)。若是用箭頭函數的寫法,就要看當前上一層的 this 指向的是哪裏了

reduce 累加器的參數中並無 thisArg 對象能夠傳,可是在回調函數中,this指向的是window。若是用箭頭函數的寫法,就要看當前上一層的 this 指向的是哪裏了

 

對於 點擊,移入移出等相似事件的回調函數 的 this 指向。

<button type="button" id="btn">點我啊</button>

function getDom(){
    console.dir(this)
}
    
// 第一種調用方法
document.getElementById('btn').addEventListener('click',function(){
    getDom(); 
})

// 第二種調用方法
document.getElementById('btn').onclick = getDom()

// 第三種調用方法
document.getElementById('btn').addEventListener('click',getDom)

// 第四種調用方法
<button type="button" id="btn" onclick="console.log(this)">點我啊</button>

(1) 第一種調用方法,this指向的是window。雖然在function(){} 回調函數裏的 this 指向的是button這個DOM對象,可是getDom是在這裏面調用的,和普通函數調用沒什麼區別。因此也指向window

(2) 第二種都不用點擊,直接觸發,this指向window。由於直接當作普通函數調用了。

(3) 第三種方法,this指向 button這個DOM對象。回調函數傳入的是函數執行的地址,執行的時候至關因而在window環境下執行,因此getDom的this指向的是window

(4) 第四種方式,this指向 button 這個DOM對象。

 當函數被用做事件處理函數時,它的this指向觸發事件的元素(一些瀏覽器在使用非addEventListener的函數動態添加監聽函數時不遵照這個約定)。 --- MDN

 

對於 當即執行函數 IIFE 中 this的指向,指向的是window

(function(){
 console.log(this) // window
})()

 

到這裏,我仍是沒搞懂下面這種狀況:

var obj={
  age:10,
  say:function(){
     function get(){
        console.log(this)   // window
    }
    get();
  }  
}
obj.say();

get函數裏的this指向的是window,由於get函數 獨立調用,並無爲內部 this 明確指向。因此會指向 window 。若是是嚴格模式,則指向undefined。

不明白的是:

(1)既然 this 指向的是window,爲何get函數在window上不能訪問?

(2)這種在函數內部定義並執行的方式,和當即執行函數有沒有區別?

(3)詞法分析的時候,這個函數是被怎樣處理的?

相關文章
相關標籤/搜索