【JavaScript】——4个手撕前端面试题(牛客题解):事件委托、合法的URL、快排、全排列

目录

1、事件委托

完整代码:

 2、合法的URL

完整代码:

3、快速排序

完整代码:

4、全排列

完整代码:


1、事件委托

请补全JavaScript代码
1. 给"ul"标签添加点击事件
2. 当点击某"li"标签时,该标签内容拼接"."符号。如:某"li"标签被点击时,该标签内容为".."
3. 必须使用DOM0级标准事件(onclick)

原本:

想要达到效果:

完整代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>事件委托</title>
</head>

<body>
    <ul>
        <li>.</li>
        <li>.</li>
        <li>.</li>
    </ul>

    <script type="text/javascript">
        // 补全代码
        // 获取ul父节点  
        // 题目要求使用事件代理,也即是不要在自身绑定onclick,我们可以选择它的父元素ul
        const ul = document.querySelector('ul')[0];

        ul.onclick = function (e) {
            // (e || window.event)为了兼容
            // target :表示当前触发事件的元素
            const tar = (e || window.event).target;
            // 如果点击的对象的名称是li
            // localName 获取标签名
            if (tar.localName === 'li') {
                tar.innerHTML = tar.innerHTML + '.';
            }
        }
    </script>
</body>

</html>

代码其实很简单,但是事件委托我们可以好好聊聊,这也是常见的面试题之一:

事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

· 为什么要用事件委托:

一般来说,dom需要有事件处理程序,我们都会直接给它设事件处理程序就好了,那如果是很多的dom需要添加相同的事件处理我们该怎么办呢?在实际开发中,使用事件委托统一由父类捕捉并处理事件,这样可以减少子类事件的重复定义

· 为什么事件委托可以性能优化:

在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间,这就是为什么性能优化的主要思想之一就是减少DOM操作的原因;如果要用事件委托,就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能;

· 那什么样的事件可以用事件委托,什么样的事件不可以用呢?

适合用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress。

值得注意的是,mouseover和mouseout虽然也有事件冒泡,但是处理它们的时候需要特别的注意,因为需要经常计算它们的位置,处理起来不太容易。

不适合的就有很多了,举个例子,mousemove,每次都要计算它的位置,非常不好把控,在不如说focus,blur之类的,本身就没用冒泡的特性,自然就不能用事件委托了。


 2、合法的URL

请补全JavaScript代码,要求以Boolean的形式返回字符串参数是否为合法的URL格式。
注意:
1. 协议仅为HTTP(S)

完整代码:

const _isUrl = url => {
            // 补全代码
            let reg = /^((https|http):\/\/)?(([A-Za-z0-9]+-[A-Za-z0-9]+|[A-Za-z0-9]+)\.)+([A-Za-z]{2,6})(:\d+)?(\/.*)?(\?.*)?(#.*)?$/g;
            return reg.test(url);
        }

这里如果不懂正则表达式什么时候用test,什么时候用match,请挪步到我上篇笔记:点击这里就能跳转

这道题就是正则表达式的考察,主要包含的知识点有:

1、表开头

2、管道符,表或者

3、表转义,比如 \\ 表示 \ , \. 表示 . 这是因为\和.在正则表达式有其他含义,想要单纯匹配\就要加转义字符\

4、 {n} 正好出现n个;{m, } 出现m次以上;{m,n} 出现m-n次

5、表0个或者1个,==={0,1} 

6、至少一个,==={1,}

7、[a-bA-Z0-9] 表任意大小写字母和数字,[]表或者

8、\d 表任意数字

9、通配符,表任意字符

10、0个或多个,==={0,}

11、.* 表任意字符有0个或多个

12、表结尾

13、全局匹配

根据题目要求判断参数URL是否合法。首先URL结构一般包括协议主机名主机端口路径请求信息哈希而本题协议已给出为HTTP(S),使用正则匹配URL,核心步骤有:

1、首先必须是以http(s)开头也可以不包含协议头部信息

2、主机名(域名)可以使用"-"符号,所以两种情况都要判断,包含"-"或不包含"-"

3、 顶级域名很多,直接判断"."之后是否为字母即可

4、最后判断端口、路径和哈希,这些参数可有可无,用?

开始符 ^

协议部分http(s)://                      表示为((https|http):\/\/)?

域名部分                                  表示为(([A-Za-z0-9]+-[A-Za-z0-9]+|[A-Za-z0-9]+)\.)+

顶级域名com cn等为2-6位         表示为([a-zA-Z]{2,6})

端口部分如:8080                      表示为(:\d+)?, ?表示0次或1次

请求路径如/login                       表示为 (\/.*)?

问号传参及哈希值如?age=1      表示为 (\?.*)?(#.*)?,路由模式传参会带#

结束符 $


3、快速排序

请补全JavaScript代码,要求将数组参数中的数字从小到大进行排序并返回该数组。
注意:
1. 数组元素仅包含数字
2. 请优先使用快速排序方法

输入:_quickSort([0,-1,1,-2,2])

输出:[-2,-1,0,1,2]

快排基本思想是通过分治来使一部分均比另一部分小(大)再使两部分重复该步骤递归)而实现有序的排列。写代码前,先了解一下快速排序的基本思路:

主要思路:

  1. 选择一个基准值midNum 
  2. 以基准值将数组分割为两部分:leftright
  3. 递归分割之后的数组直到数组为空或只有一个元素为止

完整代码:

const _quickSort = array => {
            // 补全代码
            if(array.length <=1) return array;

            // 1、取数组长度的中间数作为基准下标
            // 注意这里要用Math.floor进行 向下取整的操作,不然可能出现小数点,没办法取到数组中的数据
            let mid = Math.floor(array.length/2);
            
            // 取中间数作为基准
            let midNum = array.splice(mid,1)[0];

            // 2、将代码分为两个区域
            let left =[],right =[];
            // 3、将小于基准的数字放到left里,大于基准的放right
            for(let i=0;i<array.length;i++){
                if(array[i] < midNum){
                    left.push(array[i]);
                }else{
                    right.push(array[i]);
                }
            }

            // 3、递归:递归左区域和右区域:_quickSort(left)、_quickSort(right)
            // 4、将三者进行合并,就是最终排好队顺序
            return _quickSort(left).concat(midNum, _quickSort(right));
}

这里说下, let midNum = array.splice(mid,1)[0]和let midNum = array.splice(mid,1)的区别

比如我们排序_quickSort([0, -1, 1, -2, 2])这个数组

通过打印我们可以看到第一次找到的是1是数字,看图:

如果我们写成:  let midNum = array.splice(mid,1),输出的1就数组。

输出不一样 的原因是因为,splice(starIndex,delNum,...)返回的是被删除元素组成的数组

 两个写法是不影响最后结果的。


4、全排列

请补全JavaScript代码,要求以数组的形式返回字符串参数的所有排列组合。
注意:
1. 字符串参数中的字符无重复且仅包含小写字母
2. 返回的排列组合数组不区分顺序

输入:_permute('abc')

输出:['abc','acb','bac','bca','cab','cba']

全排列是经典的回溯算法,解决这类算法问题,最关键的就是先画图

我们用回溯三部曲来做:

1.回溯函数模板参数

        回溯算法中,for循环就是遍历这个图的宽度(即横向遍历);递归回溯就是遍历这个图的深度(即纵向遍历)
        在全排列的时候,我们每次都要从头去查找元素,所以for循环中的let i = 0 ,但是这样我们如何去重呢?就要使用到used数组。如何使用下面部分会讲。

        所以我们的backtracking的参数就是 stringused

2.回溯函数终止条件

        这里看图我们就能知道,递归到叶子节点就是我们想要的结果。也就是:

        当path中的长度==string的长度,就将一个结果添加到res数组中

3.回溯搜索的单层遍历过程

上面提到我们要用used数组去重,如何使用呢?

        

 在这个图中我们可以发现,选完a之后,在选a这条路是被剪掉的,我们剪掉的条件就是,当used数组它所对应的位置是1的时候,我们就跳过不取,这样就成功去重了。

    去重代码即为:

// 去重
if(used[i] == true) continue;

单层循环的逻辑就应该是:

每次存入path中的字母,就要将该字母对应的used数组变为1,这样我们才能去重,在回溯的时候还要将used变回0,这样逻辑才能是对的。

完整代码:

const _permute = string => {
            // 补全代码
            // res 用来保存最后输出的结果,path 表示存储的路径
            let res =[],path = [];
            // 创建一个长度为string长度的used数组,先全部初始化为0,
            let used = new Array(string.length).fill(false);
            // 调用回溯方法
            backtracking(string);
            // 返回结果
            return res;


            function backtracking(string){
                // 终止条件: 当path中的长度==string的长度,就将一个结果添加到res数组中
                if(path.length === string.length){
                    res.push(path.join(""));
                    return;
                }
                // 因为每次都要从头去找,所以循环中let i=0
                for(let i =0;i<string.length;i++){
                    // 去重
                    if(used[i] == true) continue;

                    // 剩下就是回溯,前后保持一致
                    path.push(string[i]);
                    used[i] = true;
                    backtracking(string);
                    path.pop();
                    used[i] = false;
                }
            }   

}

温馨提示:这里根本看不懂甚至不懂回溯的小伙伴们建议去看知识星球代码随想录,强烈推荐!!!

完~ 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值