函數在執行時,會在函數體內部自動生成一個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