code: function addEventListener(type, listener, capture) {
      if (!listener) return;
      if (capture === undefined) capture = false;
      if (!this._listeners) this._listeners = Object.create(null);
      if (!this._listeners[type]) this._listeners[type] = [];
      var list = this._listeners[type];
  
      // If this listener has already been registered, just return
      for(var i = 0, n = list.length; i < n; i++) {
        var l = list[i];
        if (l.listener === listener && l.capture === capture)
          return;
      }
  
      // Add an object to the list of listeners
      var obj = { listener: listener, capture: capture };
      if (typeof listener === 'function') obj.f = listener;
      list.push(obj);
    }
docs: XXX: Capturing event listeners are sort of rare.  I think I can optimize them so that dispatchEvent can skip the capturing phase (or much of it).  Each time a capturing listener is added, increment a flag on the target node and each of its ancestors.  Decrement when removed. And update the counter when nodes are added and removed from the tree as well.  Then, in dispatch event, the capturing phase can abort if it sees any node with a zero count.