事件委托

285 阅读2分钟

1. 自定义事件

1.1 浏览器自带事件

列表

1.2 自定义事件

代码

button1.addEventListener('click', () => {
  const event = new CustomEvent("zhangsan", {
    detail: {
      name: 'zhangsan',
      age: 18
    },
    bubbles: true,    // 能否冒泡
    cancelable: false // 是否阻止默认事件
  })
  button1.dispatchEvent(event)
})

div1.addEventListener('zhangsan', (e) => {
  console.log('zhangsan')
  console.log(e.detail)
})

2. 事件委托

由于冒泡阶段,浏览器从用户点击的内容从下往上遍历至 window,逐个触发事件处理函数, 因此可以监听一个祖先节点(例如爸爸节点、爷爷节点)来同时处理多个子节点的事件

2.1 场景一:给100个按钮添加点击事件

分析:监听100个按钮的祖先,等冒泡的时候判断target是不是这100个按钮中的一个 代码

div1.addEventListener('click',(e)=>{
  const t = e. target
  if(t.tagName.toLowerCase() === 'button'){
    console.log('button被点击了')
    console.log('button 的内容是'+t.textContent)
    console.log('button data-id 是'+t.dataset.id)
  }
})

2.2 场景二:监听目前不存在的点击事件

分析:监听祖先,等点击的时候看看是不是想要坚挺的元素即可 代码

setTimeout(()=>{
  const button = document.createElement('button')
  button.textContent = 'click 1'
  div1.appendChild(button)
},1000)

div1.addEventListener('click',(e)=>{
  const t = e.target
  if(t.tagName.toLowerCase() === 'button'){
    console.log('button被click点击')
  }
})

2.3 优点

  • 省监听数(内存)
  • 可以监听动态元素

3. 封装事件委托

代码

setTimeout(() => {
  const button = document.createElement('button')
  button.textContent = 'click 1'
  div1.appendChild(button)
}, 1000)

on('click','#div1','button',()=>{
  console.log('button被点击了')
})

function on(eventType,element,selector,fn){
  if(!(element instanceof Element)){
    element = document.querySelector(element)
  }
  element.addEventListener(eventType,(e)=>{
	// t是用户操作的元素
    const t = e.target
    if(t.matches(selector)){
      fn(e)
    }
  })
}

给button嵌套一层span,当点击span时(e.target === 'span'),button点击事件监听依然生效:代码

setTimeout(() => {
  const button = document.createElement('button')
  const span = document.createElement('span')
  span.textContent = 'click 1'
  button.appendChild(span)
  div1.appendChild(button)
}, 1000)

on('click', '#div1', 'button', () => {
  console.log('button 被点击了')
})

function on(eventType, element, selector, fn) {
  if (!(element instanceof Element)) {
    element = document.querySelector(element)
  }
  element.addEventListener(eventType, (e) => {
    // t是用户操作的元素
    let el = e.target
    while (!el.matches(selector)) {
      if (element === el) {
        el = null
        break
      }
      el = el.parentNode
    }
    el && fn.call(el, e, el)
  })
  return element
}

4. 整合jQuery

$('#xxx').on('click','li',fn) 代码

let dom = {
  on: function(element, eventType, selector, fn) {
    element.addEventListener(eventType, e => {
      let el = e.target
      while (!el.matches(selector)) {
        if (element === el) {
          el = null
          break
        }
        el = el.parentNode
      }
      el && fn.call(el, e, el)
    })
    return element
  },

  onSwipe: function(element, fn) {
    let x0, y0
    element.addEventListener('touchstart', function(e) {
      x0 = e.touches[0].clientX
      y0 = e.touches[0].clientY
    })
    element.addEventListener('touchmove', function(e) {
      if (!x0 || !y0) {
        return
      }
      let xDiff = e.touches[0].clientX - x0
      let yDiff = e.touches[0].clientY - y0

      if (Math.abs(xDiff) > Math.abs(yDiff)) {
        if (xDiff > 0) {
          fn.call(element, e, 'right')
        } else {
          fn.call(element, e, 'left')
        }
      } else {
        if (yDiff > 0) {
          fn.call(element, e, 'down')
        } else {
          fn.call(element, e, 'up')
        }
      }
      x0 = undefined
      y0 = undefined
    })
  },

  index: function(element) {
    let siblings = element.parentNode.children
    for (let index = 0; index < siblings.length; index++) {
      if (siblings[index] === element) {
        return index
      }
    }
    return -1
  },

  uniqueClass: function(element, className) {
    dom.every(element.parentNode.children, el => {
      el.classList.remove(className)
    })
    element.classList.add(className)
    return element
  },

  every: function(nodeList, fn) {
    for (var i = 0; i < nodeList.length; i++) {
      fn.call(null, nodeList[i], i)
    }
    return nodeList
  },
  create: function(html, children) {
    var template = document.createElement('template')
    template.innerHTML = html.trim()
    let node = template.content.firstChild
    if (children) {
      dom.append(node, children)
    }
    return node
  },

  append: function(parent, children) {
    if (children.length === undefined) {
      children = [children]
    }
    for (let i = 0; i < children.length; i++) {
      parent.appendChild(children[i])
    }
    return parent
  },
  prepend: function(parent, children) {
    if (children.length === undefined) {
      children = [children]
    }
    for (let i = children.length - 1; i >= 0; i--) {
      if (parent.firstChild) {
        parent.insertBefore(children[i], parent.firstChild)
      } else {
        parent.appendChild(children[i])
      }
    }
    return parent
  },
  removeChildren: function(element) {
    while (element.hasChildNodes()) {
      element.removeChild(element.lastChild)
    }
    return this
  },

  dispatchEvent: function(element, eventType, detail) {
    let event = new CustomEvent(eventType, { detail })
    element.dispatchEvent(event)
    return this
  },
}

5. JS支持事件吗

JS并不支持事件,事件指的是DOM事件,并不属于JS的功能,因为DOM是浏览器提供的功能,JS只是调用了DOM提供的addEventListener接口而已。