Go through the basic JavaScript skills before interview --- Event in JS
Question
a. Implement a general event listener function
b. What's the event bubbling?
c. How to listen every image click in an unlimited drop-down picture list?
Skills
1. Event binding
// general event binding function
const bindEvent = (elem, type, fn) => {
elem.addEventListener(type, fn)
}
const btn = document.getElementById('btn')
bindEvent(btn, 'click', event => {
event.preventDefault() // prevent default action
alert('click')
})
2. Event bubbling
<body>
<div id="div1">
<p id="p1">active</p>
<p id="p2">cancel</p>
<p id="p3">cancel</p>
<p id="p4">cancel</p>
</div>
<div id="div2">
<p id="p5">cancel</p>
<p id="p6">cancel</p>
</div>
</body>
// bind event
const body = document.body
bindEvent(body, 'click', event => {
console.log('body clicked')
console.log(event.target) // the element which triggers the event
})
// when click p1, the output is 'body clicked' + '<p id="p1">active</p>'
const div1 = document.getElementById('div1')
bindEvent(div1, 'click', event => {
console.log('div1 clicked')
})
// click p1 output:
// div1 clicked
// body clicked
// <p id="p1">active</p>
// click p5 output:
// body clicked
// <p id="p5">cancel</p>
Event will propagate from bottom to top following the DOM tree
3. Event delegate
Right now, we have a new demand: when you click p1, the output should be "active", and when you click p2, p3, p4, p5, output should be "cancel".
The question is: do we have to bind event for all p2, p3, p4 and p5?
// the solution is to take advantage of event bubbling
// 1. bind active event to p1
const p1 = document.getElementById('p1')
bindEvent(p1, 'click', event => {
// 3. add stopPropagation to prevent event bubbling to body
event.stopPropagation()
console.log('active')
})
// 2. bind cancel event to body
const body = document.body
bindEvent(body, 'click', event => {
console.log('cancel')
})
Let's have a look at another example:
<div id="div1">
<a href="#">a1</a>
<a href="#">a2</a>
<a href="#">a3</a>
<a href="#">a4</a>
</div>
<button>
add one a tag
</button>
We need to add click event for all a
tags, but we can't know how many a
tags exactly in div1, as the button can add a new a
tag. Use event delegate to handle this case:
const div1 = document.getElementById('div1')
div1.addEventListener('click', e => {
event.preventDefault() // prevent the a tag function
const target = e.target
if(e.nodeName === 'A'){
console.log(target.innerHTML) // this will output the a tag inner html
}
})
The advantages of event delegate:
- Make simpler code
- Save memory as we don't have to bind / unbind too many elements
- Don't abuse!
Let's refactor the general event binding function right now, as it was too simple!
const bindEvent(elem, type, selector, fn) {
// check if it's normal binding or delegate binding
if(fn==null){
fn = selector
selector = null
}
elem.addEventListener(type, event => {
const target = event.target
if(selectror){
// delegate bind
if(target.matches(selector)){
fn.call(target, event)
}
}else{
// normal bind
fn.call(target, event)
}
})
}
// when use it:
// normal binding
const p1 = document.getElementById('p1')
bindEvent(p1, 'click', function(event) { // Don't use arrow function here!
event.stopPropagation()
console.log('active')
})
// delegate binding
const div1 = document.getElementById('div1')
bindEvent(div3, 'click', 'a', function(event) {
event.preventDefault()
console.log(this.innerHTML)
})