DOM事件模型和事件委托(下)

DOM事件模型和事件委托(下)

事件委托

当监听子元素时,事件冒泡会通过目标元素向上传递到父级,直到document,如果子元素不确定或者动态生成,可以通过监听祖先素来取代监听子元素。

通俗理解:儿子办不成或难办成的事找爹办,爹也办不成的找爷爷办,总之谁有能力办成就找谁办。

情形一:给多个按钮添加点击事件,具体哪个按钮不确定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
情形一:
<div class="div1">
<button>click 1</button>
<button>click 2</button>
<button>click 3</button>
<button>click 4</button>
<button>click 5</button>
<button>click 6</button>
<button>click 7</button>
<button>click 8</button>
<button>click 9</button>
<button>click 10</button>
<button>click 11</button>
<button>click 12</button>
<button>click 13</button>
<button>click 14</button>
<button>click 15</button>
<button>click 16</button>
<button>click 17</button>
<button>click 18</button>
<button>click 19</button>
<button>click 20</button>
</div>

e.target是button,但操作哪个button并不确定,此时可以监听父元素div

1
2
3
4
5
6
7
document.querySelector('.div1').addEventListener('click', (e) => {
const t = e.target;
if(t.tagName.toLowerCase() === 'button'){
console.log('button被点击了');
console.log('button内容是:' + t.textContent);
}
});

情形二:监听原本不存在的元素

1
<div id="div2"></div>

假设div的子元素button要在一段时间之后才生成,那也不能够直接监听button,此时也可以使用事件委托。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
setTimeout(() => {
const button = document.createElement('button');
button.textContent = 'click 21';
div2.appendChild(button);
}, 500);

div2.addEventListener('click', (e) => {
const t = e.target;
// if (t.tagName.toLowerCase() === 'button') {
// console.log('新添加的button被点击');
// }
if (t.matches('button')) {
console.log('新添加的button被点击');
}
});

情形三:使用自定义函数监听不存在的元素

1
<div id="div3"></div>

自定义函数on()可以实现事件委托。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function on(eventType, element, selector, fn) {
if (!(element instanceof Element)) {
element = document.querySelector(element);
}
element.addEventListener(eventType, e => {
let el = e.target;
while (!el.matches(selector)) {
if (element === el) {
// 如果当被操作元素已经等于传入元素(最顶端元素)仍不匹配selector时,说明被操作元素不存在,跳出循环
el = null;
break;
}
// 当被造作元素不匹配selector时,就将指针移动到父元素
el = el.parentNode;
}

// 当被操作元素部位falsy时,调用fn
el && fn.call(el, e, el);
})
return element;
}

setTimeout(() => {
const button = document.createElement('button');
const span = document.createElement('span');
span.textContent = 'click 22';
button.appendChild(span);
div3.appendChild(button);
}, 500);

on.call('', 'click', '#div3', 'button', () => {
console.log('自定义函数on()被触发');
});

可以将on封装到jQuery中,使用更方便:

1
<div id="div4"></div>

以下为封装jQuery的基本方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/* 封装 */
window.$ = window.jQuery = function (selectorOrArrayOrTemplate) {
let elements;
if (typeof selectorOrArrayOrTemplate === 'string') {
if (selectorOrArrayOrTemplate[0] === '<') {
elements = [createElement(selectorOrArrayOrTemplate)];
/* 定义elements的目的是操作elements,应将其设置为数组 */
} else {
elements = document.querySelectorAll(selectorOrArrayOrTemplate);
}
} else if (selectorOrArrayOrTemplate instanceof Array) {
elements = selectorOrArrayOrTemplate;
}

function createElement(string) {
if (typeof string === 'string') {
const container = document.createElement('template');
container.innerHTML = string.trim();
return container.content.firstChild;
} else {
return 'Error! please enter a string.';
}
}

//设置jQuery原型,节约内存
const api = Object.create(jQuery.prototype);
//创建对象api。api.__proto__=jQuery.prototype
/*
api.elements = elements;
api.oldApi = selectorOrArrayOrTemplate.oldApi;
*/
Object.assign(api, {
elements: elements,
oldApi: selectorOrArrayOrTemplate.oldApi,
});
return api;
};

jQuery.fn = jQuery.prototype = {
constructor: jQuery,
jQuery: true,
// 点击事件
on(eventType, element, selector, fn) {
if (!(this.element instanceof Element)) {
this.element = document.querySelector(element);
}
this.element.addEventListener(eventType, e => {
let el = e.target;
while (!el.matches(selector)) {
if (element === el) {
// 如果当被操作元素已经等于传入元素(最顶端元素)仍不匹配selector时,说明被操作元素不存在,跳出循环
el = null;
break;
}
// 当被造作元素不匹配selector时,就将指针移动到父元素
el = el.parentNode;
}

// 当被操作元素部位falsy时,调用fn
el && fn.call(el, e, el);
})
return this;
}
}

/* 情形三 */
setTimeout(() => {
const button = document.createElement('button');
const span = document.createElement('span');
span.textContent = 'click 22';
button.appendChild(span);
div4.appendChild(button);
}, 500);

/* 调用jQuery方法 */
$('#div4').on('click', '#div4', 'button', () => {
console.log('自定义jQuery的on()函数被触发');
});

优点:

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

TL;DR 面试题

请简述事件委托。

答:

事件委托就是把事件监听放在祖先元素(如父元素、爷爷元素)上。
好处是:1 节约监听数量 2 可以监听动态生成的元素。


版权声明:本文作者为「Andy8421」.本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!