自制jQuery无法正确处理事件

时间:2017-08-13 12:59:31

标签: javascript jquery

更新:可能是jQuery的触发器()在测试中做了一些额外的工作,我在github上打开了一个issue

=====

我正在关注learnQuery来构建我的简单jQuery。现在正在处理DOM事件,实现off()06.event_listeners/runner.html函数。他们提供了一些测试,我无法通过其中一些测试。

以下是我的代码:(您可以克隆this branch,运行"use strict"; function isEmpty(str) { return (!str || 0 === str.length); } // listener use to bind to DOM element, call corresponding functions when event firing. function geneEventListener(event) { console.log('gene'); let type = Object.keys(this.handlers).find(type=>type===event.type); if (!type) return; let functions = this.handlers[type]; functions.forEach(f=>f.apply(this,event)); } // cache elements which bound event listener let Cache = function () { this.elements = []; this.uid = 1; }; Cache.prototype = { constructor:Cache, init:function (element) { if(!element.uid) element.uid = this.uid++; if(!element.handlers) element.handlers = {}; if(!element.lqListener) element.lqListener = geneEventListener.bind(element); this.elements.push(element); }, removeElement:function (uid) { this.elements.splice(this.elements.findIndex(e=>e.uid===uid),1); }, removeType:function (uid,type) { if(this.get(uid)) delete this.get(uid).handlers[type]; }, removeCallback:function (uid, type, callback) { if(this.get(uid) && this.get(uid).handlers[type]) { let functions = this.get(uid).handlers[type]; functions.splice(functions.findIndex(callback),1) } }, // return element or undefined get:function (uid) { return this.elements.find(e=>e.uid===uid); }, }; /* * One type could have many event listeners, One element could have many event types of listeners * So use element.handlers = {'click':[listener1, listener2, ...], 'hover':[...], ...} * */ let eventListener = (function() { let cache = new Cache(); function add (element, type, callback){ cache.init(element); element.addEventListener(type,element.lqListener); if(!element.handlers[type]){ element.handlers[type] = []; } (element.handlers[type]).push(callback); } // remove a type of event listeners, should remove the callback array and remove DOM's event listener function removeType (element, type) { element.removeEventListener(type,element.lqListener); cache.removeType(element.uid,type); } // remove a event listener, just remove it from the callback array function removeCallback(element, type, callback) { cache.removeCallback(element.uid,type,callback); } // bind a callback. function on(element,type,callback) { if(!(element||type||callback)) throw new Error('Invalid arguments'); add(element,type,callback); } function off(element,type,callback) { if(!(element instanceof HTMLElement)) throw new Error('Invaild element, need a instance of HMTLElement'); let handlers = cache.get(element.uid).handlers; if(isEmpty(type)&&!callback){ for(let type in handlers){ removeType(element,type); } } console.log('off') if(!isEmpty(type)&&!callback) removeType(element,type); if(!isEmpty(type) && (typeof callback === 'function')) removeCallback(element,type,callback); } return { on, off } })(); 来运行测试

element.handlers

我使用chrome调试器来关注console.log()的值,看起来很好,在添加和删除回调时效果很好。

测试在事件的回调函数中有一些console.log(),奇怪的是,这些console.log()没有登录控制台,我尝试在回调中设置断点,它也不起作用。

我的javascript经验很少,如果有人能告诉我如何调试以及bug在哪里,非常感谢!为什么/*global affix*/ /*global eventListener*/ describe('EventListeners', function() { 'use strict'; var $selectedElement, selectedElement, methods; beforeEach(function() { affix('.learn-query-testing #toddler .hidden.toy+h1[class="title"]+span[class="subtitle"]+span[class="subtitle"]+input[name="toyName"][value="cuddle bunny"]+input[class="creature"][value="unicorn"]+.hidden+.infinum[value="awesome cool"]'); methods = { showLove: function(e) { console.log('<3 JavaScript <3'); }, giveLove: function(e) { console.log('==> JavaScript ==>'); return '==> JavaScript ==>'; } }; spyOn(methods, 'showLove'); spyOn(methods, 'giveLove'); $selectedElement = $('#toddler'); selectedElement = $selectedElement[0]; }); it('should be able to add a click event to an HTML element', function() { eventListener.on(selectedElement, 'click', methods.showLove); $selectedElement.click(); expect(methods.showLove).toHaveBeenCalled(); }); it('should be able to add the same event+callback two times to an HTML element', function() { eventListener.on(selectedElement, 'click', methods.showLove); eventListener.on(selectedElement, 'click', methods.showLove); $selectedElement.click(); expect(methods.showLove.calls.count()).toEqual(2); }); it('should be able to add the same callback for two different events to an HTML element', function() { eventListener.on(selectedElement, 'click', methods.showLove); eventListener.on(selectedElement, 'hover', methods.showLove); console.log('3') $selectedElement.trigger('click'); $selectedElement.trigger('hover'); expect(methods.showLove.calls.count()).toEqual(2); }); it('should be able to add two different callbacks for same event to an HTML element', function() { eventListener.on(selectedElement, 'click', methods.showLove); eventListener.on(selectedElement, 'click', methods.giveLove); $selectedElement.trigger('click'); expect(methods.showLove.calls.count()).toEqual(1); expect(methods.giveLove.calls.count()).toEqual(1); }); it('should be able to remove one event handler of an HTML element', function() { $selectedElement.off(); eventListener.on(selectedElement, 'click', methods.showLove); eventListener.on(selectedElement, 'click', methods.giveLove); eventListener.off(selectedElement, 'click', methods.showLove); console.log('5') $selectedElement.click(); expect(methods.showLove.calls.count()).toEqual(0); expect(methods.giveLove.calls.count()).toEqual(1); }); it('should be able to remove all click events of a HTML element', function() { $selectedElement.off(); eventListener.on(selectedElement, 'click', methods.showLove); eventListener.on(selectedElement, 'click', methods.giveLove); eventListener.on(selectedElement, 'hover', methods.showLove); eventListener.off(selectedElement, 'click'); console.log('6') $selectedElement.trigger('hover'); $selectedElement.trigger('click'); expect(methods.showLove.calls.count()).toEqual(1); expect(methods.giveLove).not.toHaveBeenCalled(); }); it('should be able to remove all events of a HTML element', function() { $selectedElement.off(); eventListener.on(selectedElement, 'click', methods.showLove); eventListener.on(selectedElement, 'click', methods.giveLove); eventListener.on(selectedElement, 'hover', methods.showLove); eventListener.off(selectedElement); var eventHover = new Event('hover'); var eventClick = new Event('click'); selectedElement.dispatchEvent(eventClick); selectedElement.dispatchEvent(eventHover); expect(methods.showLove).not.toHaveBeenCalled(); expect(methods.giveLove).not.toHaveBeenCalled(); }); it('should trigger a click event on a HTML element', function() { $selectedElement.off(); $selectedElement.on('click', methods.showLove); eventListener.trigger(selectedElement, 'click'); expect(methods.showLove.calls.count()).toBe(1); }); it('should delegate an event to elements with a given css class name', function() { eventListener.delegate(selectedElement, 'title', 'click', methods.showLove); $('.title').trigger('click'); expect(methods.showLove.calls.count()).toEqual(1); }); it('should not delegate an event to elements without a given css class name', function() { eventListener.delegate(selectedElement, 'title', 'click', methods.showLove); $('.subtitle').trigger('click'); $('.title').trigger('click'); expect(methods.showLove.calls.count()).toEqual(1); }); it('should delegate an event to elements that are added to the DOM to after delegate call', function() { eventListener.delegate(selectedElement, 'new-element-class', 'click', methods.showLove); var newElement = document.createElement('div'); newElement.className = 'new-element-class'; $selectedElement.append(newElement); $(newElement).trigger('click'); expect(methods.showLove.calls.count()).toEqual(1); }); it('should trigger delegated event handler when clicked on an element inside a targeted element', function() { eventListener.delegate(selectedElement, 'title', 'click', methods.showLove); var newElement = document.createElement('div'); newElement.className = 'new-element-class'; $selectedElement.append(newElement); $('.title').append(newElement); $(newElement).trigger('click'); expect(methods.showLove.calls.count()).toEqual(1); }); it('should not trigger delegated event handler if clicked on container of delegator', function() { var $targetElement = $('<p class="target"></p>'); $selectedElement.append($targetElement); eventListener.delegate(selectedElement, 'target', 'click', methods.showLove); $selectedElement.click(); expect(methods.showLove.calls.count()).toEqual(0); }); it('should trigger delegated event handler multiple times if event happens on multiple elements', function() { eventListener.delegate(selectedElement, 'subtitle', 'click', methods.showLove); $('.subtitle').trigger('click'); expect(methods.showLove.calls.count()).toEqual(2); }); it('should not trigger method registered on element A when event id triggered on element B', function() { var elementA = document.createElement('div'); var elementB = document.createElement('div'); $selectedElement.append(elementA); $selectedElement.append(elementB); eventListener.on(elementA, 'click', methods.showLove); eventListener.on(elementB, 'click', methods.giveLove); $(elementA).trigger('click'); expect(methods.showLove).toHaveBeenCalled(); expect(methods.giveLove).not.toHaveBeenCalled(); }); }); 无法在回调中起作用。它应该有效,因为我们认为它是在测试中编写的。

以下是测试代码:

{{1}}

3 个答案:

答案 0 :(得分:1)

问题在于没有名为hover的事件。

只是mouseentermouseleave的组合。

您可以看到列出的所有事件类型aiounittest

致电element.addEventListener(type, element.lqListener)时 类型值为hover,它只是不起作用。

您可以看到此问题的更多信息here

答案 1 :(得分:1)

您非常接近满足自己的要求。这里唯一可以找到代码的问题是,Function.prototype.apply()期望第二个参数ITransactionQueryModel

  

<强>语法

Array

替代

func.apply(thisArg, [argsArray])

代表

// pass `event` as element to array literal as second parameter to `.apply()`
functions.forEach(f => f.apply(this, [event])); 

此外,functions.forEach(f => f.apply(this, event)); 的替换函数名称_Cache,因为Cache是全局定义的函数

  

缓存界面为Cache /提供了存储机制   缓存的Request个对象对,例如作为其中一部分的对象   Response生命周期。

&#13;
&#13;
ServiceWorker
&#13;
"use strict";

function isEmpty(str) {
  return (!str || 0 === str.length);
}

// listener use to bind to DOM element, call corresponding functions when event firing.
function geneEventListener(event) {
  console.log('gene');
  let type = Object.keys(this.handlers).find(type => type === event.type);
  if (!type) return;
  let functions = this.handlers[type];
  functions.forEach(f => f.apply(this, [event]));
}

// cache elements which bound event listener
let _Cache = function() {
  this.elements = [];
  this.uid = 1;
};

_Cache.prototype = {
  constructor: _Cache,
  init: function(element) {
    if (!element.uid) element.uid = this.uid++;
    if (!element.handlers) element.handlers = {};
    if (!element.lqListener) element.lqListener = geneEventListener.bind(element);
    this.elements.push(element);
  },
  removeElement: function(uid) {
    this.elements.splice(this.elements.findIndex(e => e.uid === uid), 1);
  },
  removeType: function(uid, type) {
    if (this.get(uid)) delete this.get(uid).handlers[type];
  },
  removeCallback: function(uid, type, callback) {
    if (this.get(uid) && this.get(uid).handlers[type]) {
      let functions = this.get(uid).handlers[type];
      functions.splice(functions.findIndex(callback), 1)
    }
  },
  // return element or undefined
  get: function(uid) {
    return this.elements.find(e => e.uid === uid);
  },

};

/*
 * One type could have many event listeners, One element could have many event types of listeners
 * So use element.handlers = {'click':[listener1, listener2, ...], 'hover':[...], ...}
 * */
let eventListener = (function() {
  let cache = new _Cache();

  function add(element, type, callback) {
    cache.init(element);
    element.addEventListener(type, element.lqListener);
    if (!element.handlers[type]) {
      element.handlers[type] = [];
    }
    (element.handlers[type]).push(callback);
  }

  // remove a type of event listeners, should remove the callback array and remove DOM's event listener
  function removeType(element, type) {
    element.removeEventListener(type, element.lqListener);
    cache.removeType(element.uid, type);
  }

  // remove a event listener, just remove it from the callback array
  function removeCallback(element, type, callback) {
    cache.removeCallback(element.uid, type, callback);
  }

  // bind a callback.
  function on(element, type, callback) {
    if (!(element || type || callback)) throw new Error('Invalid arguments');
    add(element, type, callback);
  }

  function off(element, type, callback) {
    if (!(element instanceof HTMLElement)) throw new Error('Invaild element, need a instance of HMTLElement');
    let handlers = cache.get(element.uid).handlers;

    if (isEmpty(type) && !callback) {
      for (let type in handlers) {
        removeType(element, type);
      }
    }
    console.log('off')
    if (!isEmpty(type) && !callback) removeType(element, type);
    if (!isEmpty(type) && (typeof callback === 'function')) removeCallback(element, type, callback);
  }

  return {
    on,
    off
  }
})();

onload = () => {

eventListener.on(document.querySelector("div"), "click", function(event) {
  console.log(event.type);
  eventListener.off(event.target, "click");
});

}
&#13;
&#13;
&#13;

答案 2 :(得分:0)

我是问题所有者。

创建issue后,他们修复了测试中的错误。在测试中,我们无法使用自制on()off()来添加事件侦听器,然后使用jQuery&#39; trigger()来测试它,因为jQuery会做一些额外的事情在后面工作。所以他们用dispatchEvent()替换了它。

另外,在我的代码中有一些错误。正如@ guest271314所述,我误用apply(),应使用call(),并应使用_Cache替换Cache。除了, 在函数removeCallback中,我误用了functions.findIndex(callback),它应该是functions.findIndex(f=>f===callback)

正确的代码位于this branch,已通过所有onoff测试。

谢谢你们所有人!