js 事件冒泡 事件代理 事件捕捉 this指標 bind this

hemeinvyiqiluoben發表於2018-03-01

轉自: http://blog.csdn.net/baidu_31333625/article/details/52750064

探討e.target與e.currentTarget

        

物件this、currentTarget和target 

在事件處理程式內部,物件this始終等於currentTarget的值,而target則只包含事件的實際目標。如果直接將事件處理程式指定給了目標元素,則this、currentTarget和target包含相同的值。來看下面的例子:

var btn = document.getElementById("myBtn");
 btn.onclick = function (event) {
     alert(event.currentTarget === this); //ture
     alert(event.target === this); //ture
 };

這個例子檢測了currentTarget和target與this的值。由於click事件的目標是按鈕,一次這三個值是相等的。如果事件處理程式存在於按鈕的父節點中,那麼這些值是不相同的。再看下面的例子:

 document.body.onclick = function (event) {
     alert(event.currentTarget === document.body); //ture
     alert(this === document.body); //ture
    alert(event.target === document.getElementById("myBtn")); //ture
 };

當單擊這個例子中的按鈕時,this和currentTarget都等於document.body,因為事件處理程式是註冊到這個元素的。然而,target元素卻等於按鈕元素,以為它是click事件真正的目標。由於按鈕上並沒有註冊事件處理程式,結果click事件就冒泡到了document.body,在那裡事件才得到了處理。

在需要通過一個函式處理多個事件時,可以使用type屬性。例如:

  var btn = document.getElementById("myBtn");
  var handler = function (event) {
          switch (event.type) {
          case "click":
              alert("Clicked");
              break;
          case "mouseover":
              event.target.style.backgroundColor = "red";
              bread;
        case "mouseout":
             event.target.style.backgroundColor = "";
             break;
        }
     };
 btn.onclick = handler;
 btn.onmouseover = handler;
 btn.onmouseout = handler;


二,bind this view plain copy
 
  1. function ReplaceProcessor() {  
  2.    this._dom = {  
  3.      btnReplace : $('#ro_btnReplace'),  
  4.      btnComplete: $('#ro_btnComplete')  
  5.    };  
  6.    // Bind events  
  7.    this._dom.btnReplace.on('click'this._onReplace.bind(this));  
  8.    this._dom.btnComplete.on('click'this._onComplete.bind(this));  
  9.  }  
  10.     
  11.  ReplaceProcessor.prototype._onReplace = function(){  
  12.   // code  
  13.   this._dom.btnComplete.html("OK");  
  14.  }  


    這裡面最後兩行程式碼是向DOM節點上繫結事件,"this._onReplace.bind(this)"明顯就是繫結的執行函式,在不知道具體作用的情況下猜測一下bind()的作用可能和call或者apply類似,用來改變function執行時的上下文環境,不知道理解的對不對所以找資料來印證一下。
    先上官網的解釋,不管我們個人的解釋是多麼的接地氣,官方API到底還是比較靠譜的:
bind方法會建立一個新函式,稱為繫結函式.當呼叫這個繫結函式時,繫結函式會以建立它時傳入bind方法的第一個引數作為this,傳入bind方法的第二個以及以後的引數加上繫結函式執行時本身的引數按照順序作為原函式的引數來呼叫原函式.
    這個解釋多讀幾次還是很靠譜的,不是很難懂。從功能描述上看和call以及apply還是有區別的,應用的場景不太一樣,bind主要是為了改變函式內部的this指向,這個是在ECMA5以後加入的,所以IE8一下的瀏覽器不支援,當然有相容的辦法,不過坦白說首先對於IE8以下實在無愛,其次那種情況下估計你也沒什麼心情用bind了吧。。。


  1. if (!Function.prototype.bind) {  
  2.   Function.prototype.bind = function (oThis) {  
  3.     if (typeof this !== "function") {  
  4.       // closest thing possible to the ECMAScript 5 internal IsCallable function        
  5.       throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");  
  6.     }  
  7.    
  8.     var aArgs = Array.prototype.slice.call(arguments, 1),   
  9.         fToBind = this,   
  10.         fNOP = function () {},  
  11.         fBound = function () {  
  12.           return fToBind.apply(this instanceof fNOP && oThis ? this : oThis || window,  
  13.                  aArgs.concat(Array.prototype.slice.call(arguments)));  
  14.         };  
  15.    
  16.     fNOP.prototype = this.prototype;  
  17.     fBound.prototype = new fNOP();  
  18.    
  19.     return fBound;  
  20.   };}  


    東西就是這麼個東西,最主要的還是應用的場景,什麼情況下使用。本文一開始程式碼中使用.bind(this)的效果就相當於將事件繫結的callback抽出來寫,但是同時還維持了函式中的this指向。本來事件繫結的處理函式一般是一個匿名函式,這裡相當於單獨抽出來從而使結構更清晰的同時,this指向的是ReplaceProcessor的例項。
    這裡列舉三部分的程式碼來說明bind能為我們做些什麼,同時它的好處在哪裡。
    (一)事件處理函式
    所謂的事件處理函式其實就是繫結事件後的那個callback,這裡如果用上bind你的程式碼應該會簡潔優雅一些,我在開篇列出的那段程式碼裡就是這樣做的。


  1. var logger = {  
  2.     x: 0,         
  3.     updateCount: function(){  
  4.         this.x++;  
  5.         console.log(this.x);  
  6.     }  
  7. }  


  
 // 下面兩段程式碼的實現是一樣的
  

  1. document.querySelector('button').addEventListener('click'function(){  
  2.     logger.updateCount();  
  3. });  


  
 document.querySelector('button').addEventListener('click', logger.updateCount.bind(logger));
    如何,這就是我之前說的,本來通常情況下處理函式都要用一層匿名函式包裹一下,才能維持處理函式本身的this.這裡直接通過.bind(logger)人為的將其執行時的this指向logger物件。
.bind()建立了一個函式,當這個函式在被呼叫的時候,它的 this 關鍵詞會被設定成被傳入的值(這裡指呼叫bind()時傳入的引數)。
    (二)setTimeout

  1. function LateBloomer() {  
  2.   this.petalCount = Math.ceil( Math.random() * 12 ) + 1;  
  3. }  
  4.    
  5. // declare bloom after a delay of 1 second  
  6. LateBloomer.prototype.bloom = function() {  
  7.   window.setTimeout( this.declare.bind( this ), 1000 );  
  8. };  
  9.    
  10. LateBloomer.prototype.declare = function() {  
  11.   console.log('I am a beautiful flower with ' + this.petalCount + ' petals!');  
  12. };  


    看一下這裡this.dclare.bind(this),相當於將LateBloomer的例項物件傳遞到declare中,是不是setTimeout簡潔了很多,同時不會破壞其他執行函式的結構。
    (三)請完整閱讀下面的程式碼

   //設立一個簡單地物件作為“上下文”
 var context = { foo: "bar" };
  
 //一個在this上下文中指向foo變數的函式
 function returnFoo () {
   return this.foo;
 }
  
 // 變數在作用域中不存在,因此顯示undefined
 returnFoo(); // => undefined
  
 // 如果我們把它繫結在context上下文中
 var bound = returnFoo.bind(context);
  
 // 現在的作用域中有這個變數了
 bound(); // => "bar"
  
 //
 // 這就是Function.prototype.bind的作用.    
 //由於returnFoo也是函式,因此它繼承了function的原型
 //
 // 如果你覺得享受,接著往下讀,下面更精彩
 //
  
 // 有許多方法將函式繫結在一個上下文中
 // Call和Apply讓你能在上下文中呼叫函式
 returnFoo.call(context); // => bar
 returnFoo.apply(context); // => bar
  
 // 將函式新增到物件中
 context.returnFoo = returnFoo;
 context.returnFoo(); // => bar
  
 //
 // 現在我們來玩一點詭異的東西
 //
  
 // Array.prototype 中有一個叫做slice的方法
 // 對一個陣列呼叫slice,可以返回一個從start index到end index的陣列
 [1,2,3].slice(0,1); // => [1]
  
 // 因此我們把Array.slice賦值給一個本地變數slice
 var slice = Array.prototype.slice;
  
 //現在的slice是"自由的",由於Array.prototype中的slice一般指定了上下文
 //或者預設為this,此時slice將不起作用
 slice(0, 1); // => TypeError: can't convert undefined to object
 slice([1,2,3], 0, 1); // => TypeError: ...
  
 // 但是如果我們使用call或者apply,slice又將在一個上下文中執行
 slice.call([1,2,3], 0, 1); // => [1]
  
 // Apply和Call差不多,知識引數要放在一個陣列中
 slice.apply([1,2,3], [0,1]); // => [1]
  
 // 使用call沒錯了,那麼能不呢使用bind呢?
 // 沒錯,我們來把"call"繫結在slice上
 slice = Function.prototype.call.bind(Array.prototype.slice);
  
 // 現在slice可以把第一個引數作為上下文了
 slice([1,2,3], 0, 1); // => [1]
  
 //
 // 很酷,對吧。現在再來完成一件事
 //
  
 // 現在我們對bind本身做一件剛才對silce做的事
 var bind = Function.prototype.call.bind(Function.prototype.bind);
  
 // 在這裡總結一下,好好想想
 // 發生了什麼事? 我們改變了call,
 // 返回一個接收一個函式和一個上下文作為ic桉樹的函式
 //並且返回了一個完全繫結的函式
  
 // 回到最初的例子
 var context = { foo: "bar" };
 function returnFoo () {
   return this.foo;
 }
  
 // 現在來使用神奇的"bind"函式
 var amazing = bind(returnFoo, context);
 amazing(); // => bar
    最後第三部分的程式碼來自一段譯文:https://variadic.me/posts/2013-10-22-bind-call-and-apply-in-JavaScript.html,程式碼很好所以忍不住拿來用,十分感謝.
    補充一段程式碼,關於Ajax的回撥中,如何保持this:

 $.ajax({
       url: url,
       type: 'post',
       dataType: 'json',
       data: {'info': info}
     })
     .done((function(data) {
       if(data.status){
         // 這裡this指向的是外層bind進來的this
         this._data.process_type = info.process_type;
       }else{
         uUnique.noticeBox.showWarning(data.message);
       }
     }).bind(this));


相關文章