鍍金池/ 問答/HTML/ 匿名回調函數(shù)中的this指向

匿名回調函數(shù)中的this指向

在平時對setTimeout addEventListener這種函數(shù)的使用,都會傳入一個匿名的callback函數(shù)

如:

1.
window.setTimeout(function() {
    console.log(this)
}, 1000) 

或者
2.
var el = document.getElementById('wrapper')
el.addEventListener('click', function() {
    console.log(this)
}, false)

上述情況1中this會指向setTimeout函數(shù)的caller -> window對象
而情況2中this也會指向addEventListener函數(shù)的caller -> wrapper這個對象

但是我自己類似于window.setTimeout這樣創(chuàng)建了一個對象

var a = {
    b: 'b in a',
    c: function (callback) {
        callback()
    }
}

//調用a對象的c函數(shù),傳入匿名函數(shù)作為參數(shù)
a.c(function() {
    //本以為this是指向a的,會輸出字符串'b in a',實際此時this指向window
    console.log(this.b) 
})

我原本以為會類似于window.setTimeout el.addEventListener那樣,this會指向.(點號)之前的對象。

然后我改了一下對象a

var a = {
    b: 'b in a',
    c: function (callback) {
        callback.bind(this)()
    }
}

這個時候this才指向a。

那么問題來了:
1.像這種匿名函數(shù)傳參的用法,為什么使用我自己定義的對象和瀏覽器提供的api產生的效果不一樣呢?這種類型的this的指向應該如何更好的理解?
2.是不是像setTimeout 、addEventListener這種系統(tǒng)api,它的內部實現(xiàn)就幫我們去把this bind給了調用這個方法的對象,如setTimeout中就有callback.bind(window)addEventListener就有callback.bind(el)?

有沒有各路大神可以解答一下,小弟感激不盡。

回答
編輯回答
祈歡
2018年1月5日 17:25
編輯回答
別傷我

你可以把this當做function的一個隱藏參數(shù),相當于

function(_this, otherArgs){

}

事實上基本所有語言也都是這么處理的。所以當this不對的時候,可以用bind顯性地傳遞這個參數(shù)。

檢查一個function是否綁定了this這個參數(shù)可以用下面的方法:

// ES5
function isBindable(func) {
  return func.hasOwnProperty('prototype');
}

// ES6
const isBindable = func => func.hasOwnProperty('prototype');

事件監(jiān)聽那里可能是做了特殊處理,畢竟JS是個設計糟糕的語言(哈)

2017年6月9日 15:14
編輯回答
薔薇花

關于this的指向的優(yōu)先級

  1. new Foo() 綁定新對象
  2. bind/call/apply 綁定指定的對象
  3. 綁定上下文

    var a = {
        b: function () {
            //this -> a
        }
    }
  4. 默認全局
var a = {
    b: 'b in a',
    c: function c2(callback) {
        callback()
    }
}

//調用a對象的c函數(shù),傳入匿名函數(shù)作為參數(shù)
a.c(function c1() {
    //c1里的this是什么,首先排除規(guī)則1,2,其次在c2函數(shù)作用域執(zhí)行,排除3,使用默認規(guī)則。
    console.log(this.b) 
})

那么樓主的代碼,為了描述我命了名。

補充 addEventListener 的說明

以下為一段為了瀏覽器兼容而提供的polyfill代碼
這段代碼結合上述規(guī)則可以很清楚的說明addEventListenerthis

(function() {
  if (!Event.prototype.preventDefault) {
    Event.prototype.preventDefault=function() {
      this.returnValue=false;
    };
  }
  if (!Event.prototype.stopPropagation) {
    Event.prototype.stopPropagation=function() {
      this.cancelBubble=true;
    };
  }
  if (!Element.prototype.addEventListener) {
    var eventListeners=[];
    
    var addEventListener=function(type,listener /*, useCapture (will be ignored) */) {
      var self=this;
      var wrapper=function(e) {
        e.target=e.srcElement;
        e.currentTarget=self;
        if (typeof listener.handleEvent != 'undefined') {
          listener.handleEvent(e);
        } else {
          listener.call(self,e);
        }
      };
      if (type=="DOMContentLoaded") {
        var wrapper2=function(e) {
          if (document.readyState=="complete") {
            wrapper(e);
          }
        };
        document.attachEvent("onreadystatechange",wrapper2);
        eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper2});
        
        if (document.readyState=="complete") {
          var e=new Event();
          e.srcElement=window;
          wrapper2(e);
        }
      } else {
        this.attachEvent("on"+type,wrapper);
        eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper});
      }
    };
    var removeEventListener=function(type,listener /*, useCapture (will be ignored) */) {
      var counter=0;
      while (counter<eventListeners.length) {
        var eventListener=eventListeners[counter];
        if (eventListener.object==this && eventListener.type==type && eventListener.listener==listener) {
          if (type=="DOMContentLoaded") {
            this.detachEvent("onreadystatechange",eventListener.wrapper);
          } else {
            this.detachEvent("on"+type,eventListener.wrapper);
          }
          eventListeners.splice(counter, 1);
          break;
        }
        ++counter;
      }
    };
    Element.prototype.addEventListener=addEventListener;
    Element.prototype.removeEventListener=removeEventListener;
    if (HTMLDocument) {
      HTMLDocument.prototype.addEventListener=addEventListener;
      HTMLDocument.prototype.removeEventListener=removeEventListener;
    }
    if (Window) {
      Window.prototype.addEventListener=addEventListener;
      Window.prototype.removeEventListener=removeEventListener;
    }
  }
})()

addEventListener polyfill 鏈接

2018年5月22日 23:05
編輯回答
命多硬

匿名函數(shù)的this默認都是指向window。對于元素事件監(jiān)聽的函數(shù)里面this指向元素自身,才屬于是特殊情況。

2018年3月12日 15:52