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


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

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

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

 情況不同,this指向的對象也不同。例如:

1.  函數聲明的情況

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()。

2.  函數表達式

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這個對象直接調用

 

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)詞法分析的時候,這個函數是被怎樣處理的?


 

更新:

以前沒搞懂為什么上面內部申明的 get()方法不能在window上訪問,其實很簡單,只是自己想復雜了而已:

get函數是在say函數里面 創建的,也就是說,不管怎么調用,get函數的作用域都只能是在say函數里面。而get方法是自執行,並沒有其它任何對象直接調用,所以this是指向window,但是作用域是say函數里面,卻不是window。感覺和立即執行函數沒區別。

不要理解成 this 指向window,產生這個this的函數就一定在window作用域上掛載。this指向 和 函數作用域 並不是相互的

 

由於js是采用的靜態作用域(也叫詞法作用域),這就意味着函數的執行依賴於函數定義的時候所產生(而不是函數調用的時候產生的)的變量作用域。

所以,函數的作用域是基於函數創建(可以理解為函數定義的時候)的地方,也就是函數在哪里創建,不管是否返回這個函數,或者返回帶括號(已執行)的函數,都不用在意,只要知道是在哪里定義即創建的就知道函數的作用域是什么內容了。

在全局作用域中“定義”一個函數的時候,只會創建包含全局作用域的作用域鏈。只有“執行”該函數的時候,才會復制創建時的作用域,並將當前函數的局部作用域放在作用域鏈的頂端。

去取變量值的時候,首先看本函數里有沒有該值,如果沒有再到函數定義的外部去找

 


更新:

  如果使用了嚴格模式,this的指向則是它進入執行環境時的值。不一定是undefined。 

"use strict"; 
function f2(){
    console.log(this) // {a: 1}
    return this;
}

f2.bind({a:1})() === undefined; // false

 

 如果未指定this,則是undefined

"use strict"; 
function f2(){
    console.log(this) // undefined
    return this;
}

f2() === undefined; // true

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM