资讯专栏INFORMATION COLUMN

jQuery 源码系列(十二)事件体系结构

Ku_Andrew / 3217人阅读

摘要:欢迎来我的专栏查看系列文章。无论的源码简单或者复杂,有一点可以肯定,致力于解决浏览器的兼容问题,最终是服务于使用者。参考源码分析事件体系结构库原生事件详解本文在上的源码地址,欢迎来。

欢迎来我的专栏查看系列文章。

前面一章,大概是一个总览,介绍了事件绑定的初衷和使用,通过了解,知道其内部是一个什么样的流程,从哪个函数到哪个函数。无论 jQuery 的源码简单或者复杂,有一点可以肯定,jQuery 致力于解决浏览器的兼容问题,最终是服务于使用者。

一些遗留问题

前面介绍 bind、delegate 和它们的 un 方法的时候,经提醒,忘记提到一些内容,却是我们经常使用的。比如 $("body").click$("body").mouseleave等,它们是直接定义在原型上的函数,不知道怎么,就把它们给忽略了。

jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " +
  "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
  "change select submit keydown keypress keyup contextmenu" ).split( " " ),
  function( i, name ) {

  // Handle event binding
  jQuery.fn[ name ] = function( data, fn ) {
    return arguments.length > 0 ?
      this.on( name, null, data, fn ) :
      this.trigger( name );
  };
} );

这个构造也是十分巧妙的,这些方法组成的字符串通过 split(" ") 变成数组,而后又通过 each 方法,在原型上对应每个名称,定义函数,这里可以看到,依旧是 on,还有 targger:

jQuery.fn.extend( {
  trigger: function(type, data){
    return this.each(function (){
      // // 依旧是 event 对象上的方法
      jQuery.event.trigger(type, data, this);
    })
  }
} )

还缺少一个 one 方法,这个方法表示绑定的事件同类型只执行一次,.one():

jQuery.fn.extend( {
  one: function( types, selector, data, fn ) {
    // 全局 on 函数
    return on( this, types, selector, data, fn, 1 );
  },
} );
DOM 事件知识点

发现随着 event 源码的不断的深入,我自己出现越来越多的问题,比如没有看到我所熟悉的 addEventListener,还有一些看得很迷糊的 events 事件,所以我决定还是先来看懂 JS 中的 DOM 事件吧。

早期 DOM 事件

在 HTML 的 DOM 对象中,有一些以 on 开头的熟悉,比如 onclick、onmouseout 等,这些就是早期的 DOM 事件,它的最简单的用法,就是支持直接在对象上以名称来写函数:

document.getElementsByTagName("body")[0].onclick = function(){
  console.log("click!");
}
document.getElementsByTagName("body")[0].onmouseout = function(){
  console.log("mouse out!");
}

onclick 函数会默认传入一个 event 参数,表示触发事件时的状态,包括触发对象,坐标等等。

这种方式有一个非常大的弊端,就是相同名称的事件,会前后覆盖,后一个 click 函数会把前一个 click 函数覆盖掉:

var body = document.getElementsByTagName("body")[0];
body.onclick = function(){
  console.log("click1");
}
body.onclick = function(){
  console.log("click2");
}
// "click2"
body.onclick = null;
// 没有效果
DOM 2.0

随着 DOM 的发展,已经来到 2.0 时代,也就是我所熟悉的 addEventListener 和 attachEvent(IE),JS 中的事件冒泡与捕获。这个时候和之前相比,变化真的是太大了,MDN addEventListener()。

变化虽然是变化了,但是浏览器的兼容却成了一个大问题,比如下面就可以实现不支持 addEventListener 浏览器:

(function() {
  // 不支持 preventDefault
  if (!Event.prototype.preventDefault) {
    Event.prototype.preventDefault=function() {
      this.returnValue=false;
    };
  }
  // 不支持 stopPropagation
  if (!Event.prototype.stopPropagation) {
    Event.prototype.stopPropagation=function() {
      this.cancelBubble=true;
    };
  }
  // 不支持 addEventListener 时候
  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

虽然不支持 addEventListener 的浏览器可以实现这个功能,但本质上还是通过 attachEvent 函数来实现的,在理解 DOM 早期的事件如何来建立还是比较捉急的。

addEvent 库

addEvent库的这篇博客发表于 2005 年 10 月,所以这篇博客所讲述的 addEvent 方法算是经典型的,就连 jQuery 中的事件方法也是借鉴于此,故值得一提:

function addEvent(element, type, handler) {
  // 给每一个要绑定的函数添加一个标识 guid
  if (!handler.$$guid) handler.$$guid = addEvent.guid++;
  // 在绑定的对象事件上创建一个事件对象
  if (!element.events) element.events = {};
  // 一个 type 对应一个 handlers 对象,比如 click 可同时处理多个函数
  var handlers = element.events[type];
  if (!handlers) {
    handlers = element.events[type] = {};
    // 如果 onclick 已经存在一个函数,拿过来
    if (element["on" + type]) {
      handlers[0] = element["on" + type];
    }
  }
  // 防止重复绑定,每个对应一个 guid
  handlers[handler.$$guid] = handler;
  // 把 onclick 函数替换成 handleEvent
  element["on" + type] = handleEvent;
};
// 初始 guid
addEvent.guid = 1;

function removeEvent(element, type, handler) {
  // delete the event handler from the hash table
  if (element.events && element.events[type]) {
    delete element.events[type][handler.$$guid];
  }
  // 感觉后面是不是要加个判断,当 element.events[type] 为空时,一起删了
};

function handleEvent(event) {
  // grab the event object (IE uses a global event object)
  event = event || window.event;
  // 这里的 this 指向 element
  var handlers = this.events[event.type];
  // execute each event handler
  for (var i in handlers) {
    // 这里有个小技巧,为什么不直接执行,而是先绑定到 this 后执行
    // 是为了让函数执行的时候,内部 this 指向 element
    this.$$handleEvent = handlers[i];
    this.$$handleEvent(event);
  }
};

如果能将上面 addEvent 库的这些代码看懂,那么在看 jQuery 的 events 源码就明朗多了。

还有一个问题,所谓事件监听,是将事件绑定到父元素或 document 上,子元素来响应,如何实现?

要靠 event 传入的参数 e:

var body = document.getElementsByTagName("body")[0];
body.onclick = function(e){
  console.log(e.target.className);
}

这个 e.target 对象就是点击的那个子元素了,无论是捕获也好,冒泡也好,貌似都能够模拟出来。接下来,可能要真的步入正题了。

总结

感觉事件委托的代码还是相当复杂的,我自己也啃了好多天,有那么一点点头绪,其中还有很多模模糊糊的知识点,只是觉得,存在就是牛逼的,我看不懂,但不代表它不牛逼。

参考

jQuery 2.0.3 源码分析 事件体系结构
addEvent库
MDN EventTarget.addEventListener()
原生JavaScript事件详解

本文在 github 上的源码地址,欢迎来 star。

欢迎来我的博客交流。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81779.html

相关文章

  • webpack4 系列教程(十二):处理第三方JavaScript库

    摘要:教程所示图片使用的是仓库图片,网速过慢的朋友请移步系列教程十二处理第三方库原文地址。因为正式项目中,由于需要的依赖过多,挂载到对象的库,很容易发生命名冲突问题。会先从安装的包中查找是否有符合的库。证明在中使用的和都成功指向了库。 教程所示图片使用的是 github 仓库图片,网速过慢的朋友请移步《webpack4 系列教程(十二):处理第三方 JavaScript 库》原文地址。或者来...

    leonardofed 评论0 收藏0
  • JavaScript专题系列文章

    摘要:专题系列共计篇,主要研究日常开发中一些功能点的实现,比如防抖节流去重类型判断拷贝最值扁平柯里递归乱序排序等,特点是研究专题之函数组合专题系列第十六篇,讲解函数组合,并且使用柯里化和函数组合实现模式需求我们需要写一个函数,输入,返回。 JavaScript 专题之从零实现 jQuery 的 extend JavaScritp 专题系列第七篇,讲解如何从零实现一个 jQuery 的 ext...

    Maxiye 评论0 收藏0
  • jQuery 源码系列(十三)事件处理源码

    摘要:专门为事件建立一个有问题,直接退出如果是一个事件处理对象,且有属性。参考源码分析事件体系结构解密事件核心绑定设计一解密事件核心委托设计二本文在上的源码地址,欢迎来。 欢迎来我的专栏查看系列文章。 通过前面一章对于 addEvent 库的介绍,它的兼容性超级棒,据说对于 IE4、5 都有很好的兼容性,这和 jQuery 的原理是一致的,而在 jQuery 中,有一个对象与其相对应,那就是...

    刘厚水 评论0 收藏0
  • 可想实现一个自己的简单jQuery库?(十二,完结篇)

    摘要:新增事件部分讲完了后我们最后实现个方法关于就常用的就种一个是取值一个是赋值我们通过判断的个数是取值还是赋值赋值很容易我们就用最简单的办法直接设置如果是取值那我们就要做个判断因为和的取法是不一样的还有一种可能性是当元素的为的时候直接取是取不 Lesson-11 新增width,height,extend 事件部分讲完了后,我们最后实现3个方法. width : function(w) ...

    ivan_qhz 评论0 收藏0
  • 可想实现一个自己的简单jQuery库?(十二,完结篇)

    摘要:新增事件部分讲完了后我们最后实现个方法关于就常用的就种一个是取值一个是赋值我们通过判断的个数是取值还是赋值赋值很容易我们就用最简单的办法直接设置如果是取值那我们就要做个判断因为和的取法是不一样的还有一种可能性是当元素的为的时候直接取是取不 Lesson-11 新增width,height,extend 事件部分讲完了后,我们最后实现3个方法. width : function(w) ...

    joyqi 评论0 收藏0

发表评论

0条评论

Ku_Andrew

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<