《JavaScript》徹底理解this指向

《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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章