javascript 中的 this 與 事件註冊 及event

來源:互聯網
上載者:User

 

本文不討論 文法解釋期和執行期的區別 以及上下文環境和閉包的概念 我們只從另一個角度來看問題.

js 中的this 是一個指標 他指象某個對象.  那麼 一般來說 記住一條原則 即可:

 

如果一個函數不是作為類 來執行個體化某個對象 如 new func();

而是當 函數 做為某個對象的 方法被調用時 則 this 就指向該對象;

如果該函數是直接被調用 則this指向全域變數...

這句話似乎不好理解. 我們看下例子:

function test(){

this.name='franky';

}

var o={};

o.t=test;

o.t();//此處test方法是作為 對象o的方法t 被調用的.則 其內部的this 就指向 對象o .則此時 test內部的 this.name='franky' 等價於 o.name='franky'

alert(o.name);//列印franky

但 如果我們 直接調用 test(); 方法 則 因為他不是作為某個對象的方法被調用. 而是直接被調用 所以 this指向了全域變數

此時 我們可以直接 alert(window.name);或 alert(name);來 訪問全域變數的 屬性name了.

 

那麼我們現在來看一看 事件註冊

事件註冊 有3種方式

1 寫入程式碼 . 即在 html element 中直接寫事件註冊.如:

 <div id="div1" onclick="alert(this.id)">測試</div>

2 js塊中的傳統方式 如:

 document.getElementById('div1').onclick=function(){alert(this.id);}

3 使用 attachEvent 和addEventListener 來註冊事件.

這當中比較經典的 則是 Jquery作者封裝的 addEvent方法.雖然他有很多不足. 具體的後面解釋. 

 

那麼現在. 我們來看看 1 寫入程式碼格式

 <div id="div1" onclick="alert(this.id)">測試</div>

為什麼這裡的this 會指向 div1 這個element呢?

答案是 ..  寫入程式碼格式 的事件註冊. 瀏覽器會把 你的代碼 封裝到一個 (匿名)函數中去.

我們此時 可以通過 列印div1的onclick屬性來 看一看

alert(document.getElementById('div1').onclick)//注意這裡不是onclick()

我們會看到 ie列印了 function anonymous(){} //看名字就知道. 他就叫匿名函數

opera 列印 function (event){}//恩. 這個是標準的匿名函數了

safari chrome ff 列印 function click(event){}// 這裡 此三個瀏覽器 給 封裝函數 一個對應事件的名字 .

 

到了這裡.我們就能看出 . 當div1 click事件發生時. 瀏覽器會做什麼呢? 是的 他會試圖去執行 div1.onclick()//如果他現在是一個方法的話

大概的過程 是  if(div1.onclick)div1.onclick();  所以呢 現在封裝函數 是作為 div1這個element對象的方法被瀏覽器調用的 則 其內部的this 指向div1 就順理成章了.

那麼 情況也就明朗了 寫入程式碼 和 js塊 中 的 document.getElementById('div1')=function(){alert(this.id)}

是一回事. 所以 此時 我們 甚至可以 直接 執行這個方法 而不是通過事件去觸發它. 即 div1.onclick();//直接執行..

但有一點 我們要注意. 我們人工 調用 該方法 不會產生瀏覽器事件. 他只會執行 alert(this.id); 這一句.

那麼 我們看看  瀏覽器事件觸發執行又是什麼過程呢?

如果您仔細看了 硬式編碼 onclick  應該不難發現 w3c瀏覽器的封裝函數 無論叫什麼 . 但他總是有個叫event的形參.

是的. 這個就是事件對象.  所以 我們在 寫入程式碼環境中 不僅僅可以 直接用this來引用 當前的element 還可以直接用event 來引用 事件對象.

而且這樣寫. 是相容全部瀏覽器的. 原因是 w3c的封裝函數有一個叫event的型參 而ie 呢? window.event可以簡寫為 event 則同樣擷取了當前的事件對象....

那麼 最後再老生常談一下. w3c瀏覽器 在事件觸發 回呼函數時 會傳過去一個參數 .但ie不會

所以 我們在非硬式編碼情況下  可以如此擷取參數

obj.onclick=function(e){

 e=e||window.event;//w3c瀏覽器 e是對象 是對象做邏輯運算時 即true 即使 是 if(new Boolen(false))//這裡也是true

                             //而ie中 因為 不會傳事件對象過來. 所以此時e==undefined.則 e=window.event 

}

好了. 這就是 為什麼 很久很久以前我們 常用的寫入程式碼註冊事件 可以直接相容所有瀏覽器的原因. 事實上 這是因為 各個瀏覽器在向 ie 致敬 ....封裝函數型參名叫event 所導致的..

但 你千萬不要以為 我們 把 型參叫event 就可以 在js塊中 不需要e=e||window.event了

比如:

obj.onclick=function(event){

event//記得 這個和寫入程式碼是不同的. 這裡的event 在ie中仍然是undefiend 這是由於 範圍所導致的. event此時是局部變數.具體原因請自己思考.

}

那麼 關於寫入程式碼 最後要提的 就只剩下一條了.  寫入程式碼環境中 我們可以省略this. 如:

 <div id="div1" onclick="alert(this.id)">測試</div>

完全等價於

 <div id="div1" onclick="alert(id)">測試</div>

各個瀏覽器都支援這樣寫 是因為 局部變數 他會先到 element的屬性工作表中去找.然後才是全域. 但我十分不建議這樣用.甚至也不建議寫入程式碼. 原因大家都能理解.

 

那麼 既然今天的主題 首先是this. 我們則接著說說this 的2個特別情況.

1.  call 和applay

    是的 這兩個方法的作用就是修改 this指標 所指向的對象. 而他們的唯一區別是傳參的方式. 具體的請去查看手冊. 這裡就不再羅嗦了. 看代碼:

function test(){

  this.name='franky';

}

var o={};

var o2={};

o.t=test;

o.t();// 此時 o.name=='franky';

但是如果是

o.t.call(o2);//只執行此句 則 o.name=='undefined' o2.name=='franky'. 是的 call 把方法t中的 this 強制指向了 o2.

這就是 call和applay的 作用 . 在js的繼承中 你可能會看到 類似下面的代碼:

function ClassA(){

this.name='franky';

}

function ClassB(){

 ClassA.call(this);//這裡執行ClassA時 只是把ClassA當作普通的 函數來執行.不同的是 通過call修改了 ClassA方法中this的引用.讓它指向了ClassB的執行個體.

}

此時. 我們 

var obj=new ClassB();  

執行個體化一個ClassB 建立一個副本即 對象obj . 我們要知道的是. ClassB() 和new ClassB() 都會導致 ClassB的執行. 即使我們寫做 var obj=new ClassB;

那麼根據js的規則. 此時 ClassB中的this會;指向obj 那麼 其內部的 ClassA.call(this); 等價於 ClassA.call(obj); 即ClassA中的this 被call 修改為指向obj

那麼 ClassA內部的 this.name='franky' 最終就等價於 obj.name='franky';//  這就實現了 動態屬性和方法的繼承.

如果我們真的理解了這裡 . 我們就應該明白. 為什麼 這種繼承方式. 無法繼承 ClassA原型屬性和方法.  而只能繼承動態屬性和方法.

再看看 new 到底幹了什麼

function NEW (Class){//山寨版的 new  Class為欲執行個體化的建構函式..

var obj={};

obj.__proto__=Class.prototype;

// __proto__屬性對於ie來說是不可見的。你可以在ff中測試  這裡是要先綁定 然後再call的

//所以 為什麼 我們可以用 在建構函式內 用 if(this instanceof arguments.callee) 來判斷 是不是 用new 來調用建構函式.

//當然 這個判斷方法 並不是100%的  如果建構函式 作為其執行個體的方法被調用 上面的條件一樣成立...不過這種需求 基本不可能出現.

var returnValue=Class.call(obj);

if(typeof returnValue=='object' &&returnValue!=null) return returnValue;

return obj;

}

 

 

到了這裡 隨著AJAX 的廣泛應用.我們不得不提一個很特別的事 就是

ie中的 xhr對象 的onreadystatechange 方法中的this 不指向xhr. 這一事實.

這非常不符合 我們所學習到的 理論體系.

var xhr=new XMLHttpRequest;

xhr.onreadystatechange=function(){this//ie中 this很遺憾的不指向xhr}

但ie7中不知道是不是因為支援了本地的 XMLHttpRequest 而不是 activex的緣故.似乎解決了這個問題.但為了相容性.

所以我們要這樣做 

xhr.onreadystatechange=function(){handler.call(xhr);}

function handler(){......}

好了. 到了這裡 基本上就把this 搞清楚了...

如果你想深入的理解 this的關係 是如何被維護的 則 可以看一看 javascript權威指南. 裡面對this引用 有更底層的說明.  可以看看js是如何通過原型鏈來維護這種關係.

 

ps: 似乎寫著寫著 就沒收住.. 越寫越多.  那麼只好把 關於 事件註冊的就簡單說一下好了  關於jquery作者的寫的 addEvent 以及他的缺點 :

1. 記憶體泄露

2.無法解決attachEvent忽略重複註冊

3.註冊同一對象的同一事件的幾個方法後 執行順序相反的問題

4. 因為 其修複了this引用問題. 卻導致了一個新的問題. 同一個建構函式的多個執行個體的 同一原型方法 註冊到 同一element的同一事件上時 會導致 _this指向最後一個被 執行個體化對象的問題. 

以上這些問題 1和4 是 其為了修複this引用而導致的新問題 而2 和3 則是attachEvent所固有的問題.

而 addEventListener 則完全沒有任何一個問題 實在是非常完美.

而 Dean Edwards 在幾年前寫了 2個版本的 addEvent  你可以去 他的blog看一看. 雖然仍然有些問題. 不過卻 也有優點.

他完全放棄了 attachEvent和addEventListener .而使用傳統方式 藉助guid 來實現 靈活註冊事件和刪除事件註冊

.那麼 所有頭疼的 問題 都來自ie的 attachEvent .所以藉助老外們常說的一句話 結束本章 : IE SUCKS !!!

 

來源:http://www.cnblogs.com/_franky/archive/2009/03/23/1420029.html

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.