刷题 | 牛客 - 前端面试手撕题 - 中等 - 1-2/20 知识点&解答

知识点总结:

DOM0级标准事件(onclick)------》dom.onclick。 如 document.querySelector('ul').onclick

事件委托,event.target 指当前点击的这个元素


FED1 事件委托

描述

请补全JavaScript代码,要求如下:

  1. 给"ul"标签添加点击事件

  2. 当点击某"li"标签时,该标签内容拼接"."符号。如:某"li"标签被点击时,该标签内容为".."

注意:

  1. 必须使用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 += '.'; }

✨ 重点解释:

  1. nodeName 是什么?

    • DOM 节点的属性 ,返回该节点的名字

    • 对于++元素节点nodeType === 1),返回的就是标签名++。

    • 在 HTML 文档中,返回的大写字符串(例如 "LI", "UL")。

    • 在 XML / XHTML 中,++nodeName 会区分大小写++ (比如你写 <Li> 就真的是 "Li")。

  2. 为什么转小写?

    • 保证兼容性,不受 HTML / XHTML / XML 的大小写差异影响。

    • 写成++nodeName.toLowerCase() === 'li'++就能稳定匹配。

  3. tagName 的区别:

    • tagName 只对 元素节点 有意义。

    • nodeName所有 节点都有意义:

      • 元素节点:返回标签名 (DIV, LI)

      • 文本节点:返回 #text

      • 注释节点:返回 #comment

    • 所以 nodeName 更通用。

  4. 这里的作用:

    • 确认点击目标确实是 <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. 数组参数仅包含数字

示例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博客


相关推荐
聪明的笨猪猪5 分钟前
Java JVM “内存(1)”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
程序员清风20 分钟前
快手二面:乐观锁是怎么用它来处理多线程问题的?
java·后端·面试
訾博ZiBo31 分钟前
【Vibe Coding】001-前端界面常用布局
前端
小烤箱32 分钟前
自动驾驶工程师面试(定位、感知向)
人工智能·面试·自动驾驶
IT_陈寒34 分钟前
《Redis性能翻倍的7个冷门技巧,90%开发者都不知道!》
前端·人工智能·后端
歪歪10044 分钟前
React Native开发Android&IOS流程完整指南
android·开发语言·前端·react native·ios·前端框架
知识分享小能手1 小时前
uni-app 入门学习教程,从入门到精通,uni-app组件 —— 知识点详解与实战案例(4)
前端·javascript·学习·微信小程序·小程序·前端框架·uni-app
ZYMFZ1 小时前
python面向对象
前端·数据库·python
长空任鸟飞_阿康1 小时前
在 Vue 3.5 中优雅地集成 wangEditor,并定制“AI 工具”下拉菜单(总结/润色/翻译)
前端·vue.js·人工智能