《JavaScript》徹底理解this指向
簡介
在學習編程的過程中,this這個關鍵字是無論如何都避免不了的一個坎,如果還沒有使用到this,那麼可以暫時忽略這篇文章中的內容,標個記號,等以後回過頭來再來了解;
關鍵字this
(本文僅在非嚴格模式下進行討論)
this是一個JS的保留關鍵字,這個單詞在JS中有特殊的含義,其意義代表:當前執行代碼的環境對象,並且有一個默認值,默認值指向的是window,如果有新指向那麼,新指向的優先級將高於默認指向;
既然是代表當前執行代碼的環境,那麼換個說法是不是可以理解成很多網上以及書籍上的說法,this的指向在定義的時候確定不了,只有在使用的時候才能確定;因爲定義且尚未使用的時候,你無法確定當前執行代碼是處於什麼環境中;
數據結構
在瞭解this之前,再明確一點,對象,函數這些的數據類型都是****引用類型,新建的時候會在內存中開闢一個位置存儲這些引用類型的值,之後,賦值給a,賦值給b這些操作,都只是將內存中的地址給賦值過去,因此,修改一個的時候纔會出現所有引用這個地址的值同步發生了改變;
let obj = {a:1} //在內存中生成了一個對象{a:1},並且將這個對象的地址賦值給了obj
let obj2 = obj; //相當於obj做了箇中介,讓obj2這個變量指向的地址是內存中的對象{a:1}
理解及示例
普通函數中的this
function demo(){
let name = "你好";
console.log(this.name); //undefined
console.log(this); //Window
}
demo();
小結:函數普通調用中的this,在沒有外界干擾的情況下(也就是call,apply等),默認指向全局對象window
對象方法中的this
var obj = {
name: '你好',
func(){
console.log(this.name) //你好
console.log(this) //{name:'你好',func:f}
}
}
obj.func()
小結:當函數作爲對象裏的方法被調用時,this默認指向的是調用該函數的對象,示例中,是obj這個對象調用了函數func(),因此指向了obj;
爲了進一步確認,對象obj再多層嵌套對象
var obj = {
name: '你好',
func(){
console.log(this.name) //你好
console.log(this) //{name:'你好',func:f}
},
b:{
name:'大家好',
func(){
console.log(this.name) //大家好
console.log(this) //{name:'你好',func:f}
}
}
}
obj.func()
obj.b.func()
按照上例的結論,this指向的是調用該函數的對象,因此,第一個是obj調用的func()函數,因此this指向的是obj,第二個是obj.b調用的func()函數,因此this指向的是obj.b,實驗符合結論,因此小結成立;
小結
- 函數普通調用中的this,在沒有外界干擾的情況下(也就是call,apply等),默認指向****全局對象window;
- 當函數作爲對象裏的方法被調用時,this默認指向的是****調用該函數的對象;
爲了驗證這個結論,在做一些更復雜的實驗:
其他示例
經典案例-對象上的方法賦值給了變量
var obj = {
name: '你好',
func(){
console.log(this.name) //undefined
console.log(this) //window
}
}
var obj2 = obj.func;
obj2()
將對象中的函數賦值給一個變量,然後運行,發現其this的值是window,而不是obj?
個人理解:其實是相同的,前文“數據結構”這一篇幅內說了,對象和函數是存在內存中的,變量只是引用了他們的地址,函數名obj.func引用的是存在內存中的函數地址,之後通過賦值操作,將obj.func引用的地址複製給了obj2,結果就是變成變量obj2直接和函數對應了起來,最終執行的時候其實和obj.func沒關係,就是普通調用了函數obj2
這個和上面得到的結論不衝突,最終調用的時候就是一個普通調用的函數,這對象方法沒有關係,那麼this指向的就是window;
經典案例-對象上的方法內包含普通函數
var obj = {
name: '你好',
func(){
function demo(){
console.log(this.name) //你好
console.log(this) //{name:'你好',func:f}
}
demo()
}
}
obj.func()
這裏面的函數執行的時候也沒有指向對象obj,這中該怎麼理解?
個人理解是:還是看函數最終的調用方式,在函數demo調用的時候,demo是作爲對象上的方法被調用的嗎,顯然不是,只是普通調用,那麼其函數體內的作用域的this就是指向的全局對象window;
同理,如果函數內包含了一個自執行的函數,那麼其內的this也會因爲沒有被誰調用,而採取默認指向window;
經典案例-匿名函數中的this
var p3 = {
sayName(){
return function() {
console.log(this) //window對象
}
}
}
p3.sayName()()
在《高程-第三版》中寫道“匿名函數的執行環境具有全局性,因此其this對象通常指向window”,那麼在正常情況下(沒有什麼騷操作改變指向),可以認爲匿名函數中的this指向的就是window;
同理,如果是setTimeout()()之類的使用的時候,其匿名函數內this指向的也就是window
構造函數
根據上面的理解函數內部的this,普通調用的時候指向的是window,但是通過構造函數創建的普通調用的卻指向的是當前函數,這是爲什麼?
先不急解答,分析一下
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this)
}
}
var p1 = new Person('oliver',18);
var p2 = Person('oliver',18);
console.log(p1)
console.log(p1.sayName())
console.log(p2)
p1和p2的區別在於p1有用到關鍵字new,按理來說函數Person並沒有return一個值,那麼打印的p1和p2都應該是undefined,但結果是
p1是一個對象,p2則是undefined,由此可見,關鍵字new在其中產生了某些操作,使得其有了一個返回值,並且返回值的類型是一個對象
//通過new關鍵字,創建了一個新對象,
Person:{
name:'oliver',
age:18,
sayName(){
console.log(this)
}
}
實際上,new執行的過程中,它會主動將其作用域內的this指向新創建的實例對象,因此,當執行到this.name,this.age等代碼時相當於在執行Person.name,Person.age;
console.log(p1.sayName())
之後,新創建的實例對象上的方法sayName自然指向的就是調用sayName的Person實例對象
說到構造函數,不得不說一種特殊情況,就是上例中Person中並沒有return,其return是由new提供的,那麼,假如定義了一個return,會怎麼樣?
function Person(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this)
}
return{ }
}
var p1 = new Person('oliver',18);
console.log(p1) //{}
如果手動設置了返回對象,那麼由new創建的默認對象就會被丟棄;
DOM中的事件處理函數
當函數被作爲事件處理時,this指向的時觸發事件的元素
// 被調用時,將關聯的元素變成藍色
function bluify(e){
console.log(this === e.currentTarget); // 總是 true
// 當 currentTarget 和 target 是同一個對象時爲 true
console.log(this === e.target);
this.style.backgroundColor = '#A5D9F3';
}
// 獲取文檔中的所有元素的列表
var elements = document.getElementsByTagName('*');
// 將bluify作爲元素的點擊監聽函數,當元素被點擊時,就會變成藍色
for(var i=0 ; i<elements.length ; i++){
elements[i].addEventListener('click', bluify, false);
}