匿名回调函数中的this指向

在平时对setTimeout addEventListener这种函数的使用,都会传入一个匿名的callback函数

如:

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函数的caller -> window对象
而情况2中this也会指向addEventListener函数的caller -> wrapper这个对象

但是我自己类似于window.setTimeout这样创建了一个对象

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

//调用a对象的c函数,传入匿名函数作为参数
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.像这种匿名函数传参的用法,为什么使用我自己定义的对象和浏览器提供的api产生的效果不一样呢?这种类型的this的指向应该如何更好的理解
2.是不是像setTimeoutaddEventListener这种系统api,它的内部实现就帮我们去把this bind给了调用这个方法的对象,如setTimeout中就有callback.bind(window)addEventListener就有callback.bind(el)?

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

阅读 873
评论 2018-04-10 提问
    4 个回答

    关于this的指向的优先级

    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函数,传入匿名函数作为参数
    a.c(function c1() {
        //c1里的this是什么,首先排除规则1,2,其次在c2函数作用域执行,排除3,使用默认规则。
        console.log(this.b) 
    })

    那么楼主的代码,为了描述我命了名。

    补充 addEventListener 的说明

    以下为一段为了浏览器兼容而提供的polyfill代码
    这段代码结合上述规则可以很清楚的说明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 链接

    评论 赞赏

      你可以把this当做function的一个隐藏参数,相当于

      function(_this, otherArgs){
      
      }

      事实上基本所有语言也都是这么处理的。所以当this不对的时候,可以用bind显性地传递这个参数。

      检查一个function是否绑定了this这个参数可以用下面的方法:

      // ES5
      function isBindable(func) {
        return func.hasOwnProperty('prototype');
      }
      
      // ES6
      const isBindable = func => func.hasOwnProperty('prototype');

      事件监听那里可能是做了特殊处理,毕竟JS是个设计糟糕的语言(哈)

      评论 赞赏
        评论 赞赏

          匿名函数的this默认都是指向window。对于元素事件监听的函数里面this指向元素自身,才属于是特殊情况。

          评论 赞赏
            撰写回答

            登录后参与交流、获取后续更新提醒