H5W3
当前位置:H5W3 > 其他技术问题 > 正文

【Web前端问题】匿名回调函数中的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)?

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

回答:

关于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是个设计糟糕的语言(哈)

回答:

MDN bind

回答:

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

本文地址:H5W3 » 【Web前端问题】匿名回调函数中的this指向

评论 0

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址