知识点总结:
DOM0级标准事件(onclick)------》dom.onclick。 如 document.querySelector('ul').onclick
事件委托,event.target 指当前点击的这个元素
FED1 事件委托
描述
请补全JavaScript代码,要求如下:
-
给"ul"标签添加点击事件
-
当点击某"li"标签时,该标签内容拼接"."符号。如:某"li"标签被点击时,该标签内容为".."
注意:
- 必须使用DOM0级标准事件(onclick)
解答
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
/* 填写样式 */
ul {
list-style: none;
padding: 0;
}
li{
cursor: pointer;
font-size: 20px;
padding: 5px;
}
li: hover{
background-color: #f0f0f0;
}
</style>
</head>
<body>
<ul>
<li>.</li>
<li>.</li>
<li>.</li>
</ul>
<!-- 填写标签 -->
<script type="text/javascript">
// 填写JavaScript
document.querySelector('ul').onclick = event => {
// 法一:事件委托 + tagName
if(event.target.tagName === 'LI'){
event.target.innerHTML += '.';
}
// 法二:事件委托+innerText,潜在问题:点击到非li的ul区域内,会在空白处添加.
event.target.innerText += '.'
// 法三:事件委托 + nodeName,将其转小写
event = event || window. Event;
if(event.target.nodeName.toLowerCase() === 'li'){
event.target.innerText += '.'
}
}
// 拓展:选中所有的 li,遍历每个li, 给它绑定点击事件
// 结果:对本事件无效。原因:样例输入也是 DOM0级,每个事件只能绑定一次,后面会覆盖前面的。
/* const lis = document.querySelectorAll('li');
lis.forEach(li => {
li.onclick = function(){
this.innerHTML += '.';
}
}) */
</script>
</body>
</html>
分析:
法一:事件委托 + tagName
if (event.target.tagName === 'LI') { event.target.innerHTML += '.'; }
-
tagName
返回元素的标签名(HTML 下总是大写 ,比如"UL"
,"LI"
)。 -
所以必须写成
"LI"
才能匹配。 -
优点:直观、常见。
-
缺点:大小写敏感,不同文档类型(HTML vs XHTML)可能行为不同。
法二:事件委托 + innerText
event.target.innerText += '.';
-
不做判断,直接加点。
-
风险 :如果++点到的是
<ul>
空白区++ ,++event.target
变成<ul>
,那就会在整个<ul>
的文本后面加点,逻辑错误。++ -
优点:代码简洁。
-
缺点:不安全,容易误操作。
法三:事件委托 + nodeName.toLowerCase()
event = event || window.event; // 兼容性写法
if (event.target.nodeName.toLowerCase() === 'li') { event.target.innerText += '.'; }
✨ 重点解释:
-
nodeName
是什么?-
DOM 节点的属性 ,返回该节点的名字。
-
对于++元素节点 (
nodeType === 1
),返回的就是标签名++。 -
在 HTML 文档中,返回的大写字符串(例如
"LI"
,"UL"
)。 -
在 XML / XHTML 中,++
nodeName
会区分大小写++ (比如你写<Li>
就真的是"Li"
)。
-
-
为什么转小写?
-
保证兼容性,不受 HTML / XHTML / XML 的大小写差异影响。
-
写成++nodeName.toLowerCase() === 'li'++就能稳定匹配。
-
-
和
tagName
的区别:-
tagName 只对 元素节点 有意义。
-
nodeName 对 所有 节点都有意义:
-
元素节点:返回标签名 (DIV, LI)
-
文本节点:返回 #text
-
注释节点:返回 #comment
-
-
所以
nodeName
更通用。
-
-
这里的作用:
-
确认点击目标确实是
<li>
元素,而不是<ul>
或其它节点。 -
toLowerCase()
保证跨浏览器 / 文档类型下都能稳定匹配。
-
✅ 总结:
法一 :常规做法,简单直接,但大小写固定要写
"LI"
。法二:简洁,但有潜在 bug(点空白会误加)。
法三:最稳妥的写法 ------
用
nodeName
判断,适用于更多情况。转小写避免大小写不一致问题。
推荐在写通用组件 / 库的时候用这种方式。
知识点:事件委托(event) & DOM0级标准事件(onclick)
在 HTML 中,tagName
默认返回 大写
关键点: event.target
- ++event.target 表示 实际被点击的那个元素++
👉 在 DOM 里,点击"空白"其实也是点到某个元素。比如:
点击
- → event.target 是这个
点击
的 padding / margin 区域 → event.target 是
本身
事件委托(event.target)
- 只绑定一次事件在
ul
上,节省内存,适合元素很多、动态添加的场景。逐个绑定(①const lis = document.querySelectorAll('li'); ②lis.forEach(li => { li.onclick = function(){} }))
- 每个
<li>
单独有事件,代码直观,但如果<li>
很多,性能稍差。
FED2 数组去重
描述
请补全JavaScript代码,要求去除数组参数中的重复数字项并返回该数组。
注意:
- 数组参数仅包含数字
示例1
输入:
_deleteRepeat([-1,1,2,2])
输出:
[-1,1,2]
法一:ES6 的 Set (最简洁,O(n))
javascript
return [...new Set(array)];
或者
return Array.from(new Set(array));
new Set(array) 返回类数组对象,Array.from 将类数组对象转为真正的数组。
法二:传统写法(进阶)(如 新数组 + includes)
javascript
const arr = [];
array.forEach(item => {
if(!arr.includes(item)){
arr.push(item);
}
})
forEach 是元素值循环,即 item是元素值 ---》forEach 第一个参数是 元素值,第二个参数是 索引。------》forEach((item, index) => ...)
for是索引循环
时间复杂度是 O(n²),对大数组性能不太好
法三:算法思路(排序+相邻比较)
javascript
array.sort((a, b)=> a - b);
const arr = array.filter((item, i) => i === 0 || item !== array[i - 1])l
先用.sort 进行排序,再用 filter 进行过滤,保留第一个元素 以及 之后的 元素与前一个元素不同的,即不重复的;否则,过滤掉。
时间复杂度是O(n log n) 。
但是会改变原数组顺序,适合只关注结果、不关心顺序的情况。
法四:哈希表(性能更优)
javascript
const seen = {};
const arr = array.filter(item => {
if(!seen[item]){
seen[item] = true;
return true;
}
return false;
});
使用 普通对象 当作哈希表,时间复杂度是O(n),性能更好。
缺点:对象键会被转为字符串,比如数字 1 和字符串 "1" 会冲突。------》解决:使用 Map
法五: ★ 对象数组去重(★ 实战场景:Map按 id 去重)
数组里存的是对象:[{id:1,name:'A'},{id:1,name:'A'},{id:2,name:'B'}] 。
使用 Map 按 id 去重
javascript
const arr = [...new Map(
array.Map(item => [item.id, item])
).values()];
保留第一次出现的对象,同时去除重复id的对象。
在实际项目中常见,如接口返回的用户列表去重。
总结 :最简洁 用 Set ------》思路 用排 序 ------》性能 用 哈希表或Map ------》 实际开发常见 对象数组按 id 去重 。
所有的代码,以及其他方法(上述是几个重要的方法):
html
const arr = [...new Map(
array.Map(item => [item.id, item])
).values()];<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8>
</head>
<body>
<script type="text/javascript">
const _deleteRepeat = array => {
// 补全代码
/* 数组去重 */
// 法一:arr.includes + push(不重复者 添加到新数组中)[以下4种 即可]
let arr = []
let len = array.length;
/*for(let i=0;i < len;i++){
if(!arr.includes(array[i])){
arr.push(array[i]);
}
}
return arr; */
// 注意点:forEach 中的参数 item 是对应的值
/* array.forEach(item => {
if(!arr.includes(item)){
arr.push(item);
}
})
return arr;*/
// map 中属性名不可重复
/* const map = new Map();
array.forEach(item => {
if(!map.has(item)){
map.set(item, true);
arr.push(item);
}
})
return arr; */
// 对象属性名不可重复
/* const obj = {}
array.forEach(item => {
if(!obj[item]){
obj[item] = true;
arr.push(item);
}
})
return arr;*/
// 法二:Set + Array.from 方法(es6方法):new Set(array) 返回类数组,可以用 Array.from 转为真正的数组
// return Array.from(new Set(array));
return [...new Set(array)];
// 法三:数组的 reduce 累加器(累加数,当前的值)
/* const newArr = array.reduce((pre, cur) => {
if(pre.includes(cur) === false){
pre.push(cur);
}
return pre;
}, []);
return newArr;*/
}
</script>
</body>
</html>
知识点:数组去重总结,见 手撕代码 | 知识点总结 - 数组去重-CSDN博客