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

欢迎来我的专栏检察系列文章。

前面一章,大概是一个总览,引见了事宜绑定的初志和运用,经由过程相识,晓得其内部是一个什么样的流程,从哪一个函数到哪一个函数。不管 jQuery 的源码简朴或许庞杂,有一点能够一定,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<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 的浏览器能够完成这个功用,但本质上照样经由过程 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。

欢迎来我的博客交换。

    原文作者:songjz
    原文地址: https://segmentfault.com/a/1190000008494176
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞