正则面试题汇总

1,755 阅读6分钟

前言

正则表达式是匹配模式,要么匹配字符,要么匹配位置。关于正则表达式的原理和写法,网上的文章已经特别之多了,本篇文章将不着重于原理与方法,将直接以各种题目的形式来进行正则表达式的介绍,同样,题目的难度也将由弱至强。开始题目可能没那么多,后序还会进行补充哒。首先附上经典的正则练习网站:regulex

基本符号

字符组用于纵向模糊匹配,[]组内代表一个字符元素
^在第一位放脱字符,表示求反的概念
\d[0-9],digit,表示一位数字
\D[^0-9],表示除数字外的任意字符
\w[0-9a-zA-Z],word,表示数字、大小写字母和下划线等单词字母
\W[^0-9a-zA-Z],表示非单词字母
\s[\t\v\n\r\f],space,表示空白格,包括空格、水平制表符、垂直制表符、换行符、回车符、换页符
\S[^ \t\v\n\r\f],表示非空白符
.[^\n\r\u2028\u2029],通配符,表示几乎任意字符,换行、回车、行分隔、段分隔符除外。
\1反向引用,代表第一个用括号区分的分组,括号嵌套的号由开括号(来判断
量词用于横向匹配,匹配多个字符,默认为贪婪匹配,可后加?变为惰性匹配
{m,}表示至少出现m次
等价于{0,1},表示出现或不出现
+等价于{1,},表示出现至少一次
*等价于{0,},表示出现任意次或者不出现
位置特性用于位置方式的匹配
^、$脱字符、美元符号分别在多行匹配中,匹配行开头与行结尾
\b匹配单词边界,具体就是\w与\W之间的位置,也包括\w与^、$之间的
\B同样,是\b的反面的意思
(?=p)p为一个子模式,即匹配p前面的位置
(?!p)其实就是(?=p)取反,除了p前面的位置以外的所有位置
方法返回值与参数
test一个在字符串中测试是否匹配的RegExp方法,它返回true或false。regex.test(string)
exec一个在字符串中执行查找匹配的RegExp方法,它返回一个数组(未匹配到则返回null)regex.exec(string)
match一个在字符串中执行查找匹配的String方法,它返回一个数组或者在未匹配到时返回null。string.match(regex);match返回的是一个数组,先是整体匹配结果,然后是各个括号匹配的内容,匹配下标、输入文本
search一个在字符串中测试匹配的String方法,它返回匹配到的位置索引,或者在失败时返回-1。
replace个在字符串中执行查找匹配的String方法,并且使用替换字符串替换掉匹配到的子字符串。string.replace(regex, “ ”)
split一个使用正则表达式或者一个固定字符串分隔一个字符串,并将分隔后的子字符串存储到数组中的String方法。

简单正则匹配

字符匹配

16进制颜色值

要求匹配:#12f3a1,#ffBabd,#FFF;等颜色值字母不区分大小写,且可为3位或者六位

let regex = /#([0-9a-fA-F]{6}|[0-9a-fA-F]{3}/g;
let string = "#12f3a1 #ffBabd #FFF #123 #586";
console.log(string.match(regex));

相当简单的正则,主要就是把基础知识运用下,只有两个分支匹配且逻辑清楚。

24小时时间

要求匹配:23:59,04:09,8:9,19:47;这样的时间,前面的0可以省略也可带着

let regex = /^(0?[0-9]|1[0-9]|2[0-3]):(0?[0-9]|[1-5][0-9])$/;
let arr = ["23:59", "04:09", "8,9", "19:47"];
let res = [];
for (let i = 0; i < arr.length; i++) {
    if (regex.test(arr[i])) {
        res.push(arr[i]);
    }
}

也是比较简单的正则,记得用括号将各段隔开,不然运算顺序可能不与你想的一致。

IP地址

要求匹配:192.168.225.255,156.234.156.215,1.2.3.4;类似的IP地址

let regex = /^(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.\1\.\1\.\1$/;
let arr = ["192.168.225.255", "156.234.156.215", "1.2.3.4"];
let res = [];
for (let i = 0; i < arr.length; i++) {
    if (regex.test(arr[i])) {
        res.push(arr[i]);
    }
}

这里的.字符需要转义,且利用括号分组来复用前面的正则。\1代表第一个括号引用的分组,根据第一个开括号(来确认。

带格式日期

要求匹配:2020-09-12,2043-12-30,2018/08/09,2016.06.21;分隔符有三种可用,且要求分隔符前后使用一样。

let regex = /\d{4}(-|\/|\.)\d{2}\1\d{2}/;
let res = [];
let arr = ["2020-09-12""2043-12-30""2018/08/09""2016.06.21"];
for (let i = 0; i < arr.length; i++) {
    if (regex.test(arr[i])) {
        res.push(arr[i]);
    }
}

要求分隔符前后一致,因此我们就必须用上题提过的括号分组复用,才能够实现这样的功能。我这里没有对日期的期限进行要求,直接用的数字均可,有要求的话,根据之前IP例题的逻辑,也能够很容易写出,这里就不再赘述。

同样,分组复用可以实现简单的替换,例如:想把yyyy-mm-dd替换成mm/dd/yyyy,以下的代码可以实现

let regex = /(\d{4})-(\d{2})-(\d{2})/;
let string = "2020-09-12";
let res = string.replace(regex, function() {
    return RegExp.$2 + "/" + RegExp.$3 + "/" + RegExp.$1;
});
RegExp中的$1、$2、$3代表第123个分组

字符串去重

实例:将"aaaaabbbbbccccc"去重返回成为"abc"。利用括号分组的同样匹配,来实现查询出重复字符的效果,由于重复字符可以出现多次,因此后面还要加上+,表示至少出现一次。

let regex = /(\w)\1+/g;
let str = "aaaaabbbbbddddggggggggffff";
let res = str.replace(regex, "$1");

HTML标签

要求匹配HTML标签a内容,比如"""",即匹配出两个<>之间,且第一个字符为a

let regex = /<a[^>]+>/g

这里的^脱字符代表取反,即除了">"以外均匹配。

驼峰字符串

要求将一个连续字符串如:get-element-by-id转化成驼峰形式

function toHump(str) {
    return str.replace(/-(\w)/g, function($1))
}

将-后面是字符的-与首字符的位置均匹配到,然后将$1,即-后的字符变成大写后replace进去

邮箱格式

1.不限制长度

2.不限制大小写

3.邮箱开头必须是数字或字符串

4.邮箱中可以使用字母、数字、点号、下划线、减号,但是不能连写点号、下划线、减号,如 abc_-de@q_.q.com

5.@符号前后不能为点号、下划线、减号

function isAvailableEmail(sEmail) {
    let reg = /^([\w+\._-])+@\w+[\.\w]+$/;
    return reg.test(sEmail);
}

识别十进制整数

修改 js 代码中 parseInt 的调用方式,使之通过全部测试用例。

eg:'12px'、'0x12'

function parse2Int(num) {
    var regex=/^\d+/;
    num=regex.exec(num)[0];
    return parseInt(num);
};
//或者可以直接使用parseInt(num, 10);这个API会自动识别非十进制的数,将其排除在外
function parse2Int(num) {
    return parseInt(num,10);
}

颜色字符串转换

考的知识点两个:

  1. 正则表达式匹配;

  2. toString(16)转换进制;toString()可用于转换进制、判断引用类型。

做的过程中注意:

  1. 数值超界(0-255)
  2. 不足两位补零
function rgb2hex(sRGB) {
    let reg = /rgb\((\d+),\s*(\d+),\s*(\d+)\)/;
    let res = sRGB.match(reg);//match返回一个数组或者在未匹配到时返回null
    if (!res) {
        return sRGB;
    } else {
        let str = '#';
        for (let i = 1; i <= 3; i++) {
            let num = parseInt(res[i]);
            if (num <= 255 && num >= 0) {
                str += (num < 16 ? '0' + num.toString(16) : num.toString(16));
            } else {
                return sRGB;
            }
        }
        return str;
    }
}

变为驼峰

replace() 方法返回一个由替换值(replacement)替换一些或所有匹配的模式(pattern)后的新字符串。

这个用法的本质就是:对str使用RegArg做match()匹配,如果匹配到多项结果(比如使用了全局匹配g,或者分组),那么每一个匹配结果都将执行一次FuncArg函数,并且用该函数的返回值替代源字符串中的匹配项。

第一个参数可以是字符串或正则表达式,如果提供的是字符串,只会替换第一个子字符串。如果想替换所有子字符串,需要提供一个指定了 g 的正则表达式。

第二个参数可以是字符串或函数。如果是字符串,可以使用一些特殊的 字符序列

如果第二个参数也可以是函数,这个函数接收多个参数:function (match[,p1, p2, ..., pn], offset, string)

  • match:匹配的子串,等同于前面提到的 $&
  • p1-p2:为捕获组对应的匹配字符串(如果设置了捕获组)。
  • offset:模式匹配项位于输入字符串的位置
  • string:输入的原始字符串。
  • 函数的返回值:返回值即为替换的文本。

css 中经常有类似 background-image 这种通过 - 连接的字符,通过 javascript 设置样式的时候需要将这种样式转换成 backgroundImage 驼峰格式,请完成此转换功能

1、以 - 为分隔符,将第二个起的非空单词首字母转为大写

2、-webkit-border-image 转换后的结果为 webkitBorderImage

function cssStyle2DomStyle(sName) {
    let reg = /-(\w)/g;
    let res = sName.replace(reg, function(fullMatch, g1, index) {
        if (index === 0) return g1;
        //当模式串匹配的为单词的首项时,证明-在最前面,此时返回小写字母,将-a替换成a。
        return g1.toUpperCase();
    });
    return res;
}

获取url中参数

  1. 指定参数名称,返回该参数的值 或者 空字符串

  2. 不指定参数名称,返回全部的参数对象 或者 {}

  3. 如果存在多个同名参数,则返回数组

例:输入:www.nowcoder.com?key=1&key=2&key=3&test=4#hehe key;输出:[1, 2, 3]

方法一:使用字符串拼接来匹配。

/*  获取URl中的参数
* @para url 
* @para key 参数名*/
function getUrlParam(sUrl, sKey) {
    var left= sUrl.indexOf("?") + 1
    var right= sUrl.lastIndexOf("#")
    var parasString = sUrl.slice(left, right)
    var paras = parasString.split('&');
    var parasjson = {}
    paras.forEach(function (value, index, arr) {
        var a = value.split('=');
        parasjson[a[0]] !== undefined ? parasjson[a[0]] = [].concat(parasjson[a[0]], a[1]) : parasjson[a[0]] = a[1];
    });

    let result = arguments[1] !== void 0 ? (parasjson[arguments[1]] || '') : parasjson;
    return result
}

方法二:使用正则中的replace进行替换

function getUrlParam2(sUrl, sKey) {
    var result, Oparam = {};
    sUrl.replace(/[\?&]?(\w+)=(\w+)/g, function ($0, $1, $2) 
        console.log('$0:' + $0 + "     $1:" + $1 + "     $2:" + $2);
        Oparam[$1] === void 0 ? Oparam[$1] = $2 : Oparam[$1] = [].concat(Oparam[$1], $2);
    });
    sKey === void 0 || sKey === '' ? result = Oparam : result = Oparam[sKey] || '';
    return result;
}

方法三:使用正则中的exec方法

function getUrlParam3(sUrl, sKey) {
    var resObj = {};
    var reg = /(\w+)=(\w+)/g;
    while (reg.exec(sUrl)) {
        resObj[RegExp.$1] ? resObj[RegExp.$1] = [].concat(resObj[RegExp.$1], RegExp.$2) : resObj[RegExp.$1] = RegExp.$2;
    }
    if (sKey) {
        return (resObj[sKey] ? resObj[sKey] : '');
    }
    return resObj;
}

域名解析

函数parseUrl实现将一段url字段解析为Object,例如url为:"www.xiyanghui.com/product/lis…

let object = {
    protocol:"http",
    host:"www.xiyanghui.com",
    path:"product/list",
    query: {
        id:"123456",
        sort:"discount"
    },
    hash:"title"
}

function parseUrl(str) {
    // 判断是否传入参数
    if (str) {
        var obj = {};
        var queryArr = [];
        // 正则表达式规则
        var re = /^(http[s]?):\/\/([0-9a-zA-Z\.]+)\/([a-zA-Z0-9\/]+)\?([a-zA-Z0-9\=\&]+)#([0-9a-zA-Z\.]+)$/;
        // 利用正则表达式将字符串分组
        var reArr = re.exec(str);
        if (reArr) {
            obj.peotocol = reArr[1];
            obj.host = reArr[2];
            obj.path = reArr[3];
            queryArr = reArr[4].split(/[\&\=]+/);
            obj.query = {};
            for (var i = 0; i < queryArr.length; i += 2) {
                obj.query[queryArr[i]] = queryArr[i + 1];
            }
            obj.hash = reArr[5]
            return obj;
        } else {
            return null;
        }
    } else {
        return null;
    }
}

位置匹配

千位分隔符

千位分隔符的关键在于两点:1、使用量词+多次匹配d{3};2、要求匹配的位置不能是开头,因此用(?!^)

let regex = /(?!^)(?=(\d{3})+$)/g;
let string = "123456789";
let res = string.replace(regex, ',');

如果还要求支持"12345783 23498237489 3219482"这样多个数字间用空格来区分的输入的话,只需要将^、$进行修改,改成\b,从而匹配多个连续字符的开头、结尾,而不是匹配整个字符串的开头、结尾。

let regex = /(?!\b)(?=(\d{3})+\b)/g;
let string = "12345783   23498237489  3219482";
let res = string.replace(regex, ',');

密码判断

多种条件的密码判断往往写成多个小的正则进行判断,下面基本使用三个小的条件进行密码判断。总的要求是:密码长度6-12位,由数字、小写字符、大写字符组成,但必须至少包括2种字符,且必须包含数字

//1、要求密码长度6-12位
let regex1 = /^[0-9A-Za-z]{6,12}$/;
//2、要求必须包含数字
let regex2 = /?=.*[0-9]/;//即任意数量任意字符的后面前一个位置,后面会接一个数字=>包含一个数字
//3、要求同时包含两种
let regex3 = /(?=.*[0-9])(?=.*[a-z])/;
let arr = ["192.168.225.255", "156.234.156.215", "1.2.3.4"];
let res = [];
for (let i = 0; i < arr.length; i++) {
    if (regex1.test(arr[i]) && regex2.test(arr[i]) && regex3.test(arr[i])) {
        res.push(arr[i]);
    }
}

替代正则

并不是所有的问题均适合使用正则解决:1、有些看似简单的问题,但正则做不到;2、有些能用字符串简单API就能解决的问题,使用正则会提高时间负杂度。

提取年月日

let string = "2020-09-01";
let regex = /^(\d{4})-(\d{2})-(\d{2})/;
console.log(string.match(regex));
//输出为["2020-09-01", "2020", "09", "01", index: 0, input: "2020-09-01"]

//其实,用字符串的split方法来做即可:
let string = "2020-09-01";
let res = string.split("-");
console.log(res);//输出为["2017", "07", "01"]
/*split()方法用于把一个字符串分割成字符串数组。
separator	必需。字符串或正则表达式,从该参数指定的地方分割 stringObject。
howmany	可选。该参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。*/

模糊查询

需要实现的功能是类似百度搜索框的模糊查询,这里先只考虑JS代码,构建的函数传入参数有2个,分别是list存储所有关键词信息的数组、keyword模糊查询的关键词、res查询得出的结果。

indexOf方法

stringObject.indexOf(searchValue)该方法从头到尾检索字符串stringObject,看它是否含有子串searchValue,开始检索得位置在字符串的开头,找到searchValue时,则返回其第一次出现的位置,没有找到则返回-1。

function fuzzyQuery (list, keyWord) {
    let res = [];
    for (let i = 0; i < list.length; i++) {
        if (list[i].indexOf(keyWord) >= 0) {
            res.push(list[i]);
        }
    }
    return res;
}

split方法

stringObject.split(separator)。该方法通过在separator指定的边界处将字符串stringObject分割成子串并返回子串数组。返回的数组中的字串不包括separator自身。如果stringObject中不存在separator,将返回一个只包含stringObject的数组。故可以根据返回数组的长度来判断是否存在子字符串separator

function fuzzyQuery(list, keyWord) {
    let res = [];
    for (let i = 0; i < list.length; i++) {
        if (list[i].split(keyWord).length > 1) {
            res.push(list[i]);
        }
    }
    return res;
}

match方法

该方法在字符串内检索指定的值,或找到一个或多个正则表达式的匹配;如果没有找到任何匹配的文本,将返回null。否则,将返回一个数组。

function fuzzyQuery(list, keyWord) {
    let res = [];
    for (let i = 0; i < list.length; i++) {
        if (list[i].match(keyWord) != null) {
            res.push(list[i]);
        }
    }
    return res;
}

test方法

function fuzzyQuery(list, keyWord) {
    let reg = new RegExp(keyWord);
    let res = [];
    for (let i = 0; i < list.length; i++) {
        if (reg.test(list[i])) {
            res.push(list[i]);
        }
    }
    return res;
}

搜索框模糊查询实现

简单html实现

主要关注html实现,就不用上述定义的函数了,而是用更简单的方式。

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title></title>
<script type="text/javascript">
onload = function () {
    //onload事件,页面在加载完成时马上执行的一组代码
    function handle() {
        var keyWords = { 
            "a": ["abada", "asdkasdfda", "askfdlf"],
            "b": ["bfsdifdpa", "杨振宇", "杨过"],
            "c": ["cdfdfgd", "cgfhjf", "cuyjk"],
            "d":["dfdgd","dyjhfh","dhyjgh"]
        };
        if (keyWords[this.value]) {
            //判断body中是否有这个层,如果有就删掉了
            if (document.getElementById('dv')) {
                document.body.removeChild(document.getElementById('dv'));
            }
            //开始创建层
            var dvObj = document.createElement('div');
            dvObj.id = 'dv';
            dvObj.style.width = '300px';
            //dvObj.style.height = '200px'; //将来可以不要
            dvObj.style.border = '1px solid red';
            document.body.appendChild(dvObj);
            //脱离文档流
            dvObj.style.position = 'absolute';
            dvObj.style.left = this.offsetLeft + 'px';
            dvObj.style.top = this.offsetHeight + this.offsetTop + 'px';
            //循环创建
            for (var i = 0; i < keyWords[this.value].length; i++) {
                //创建一个可以存文本的标签
                var pObj = document.createElement('p');
                pObj.innerText = keyWords[this.value][i];
                //p标签要有小手,还有高亮显示
                pObj.style.cursor = 'pointer';
                pObj.style.margin = '5px';
                pObj.onmouseover = function () {
                    this.style.backgroundColor = 'red';
                };
                pObj.onmouseout = function () {
                    this.style.backgroundColor = '';
                }
                dvObj.appendChild(pObj); //操作节点,把p标签加到层中
                //同样可以用insertBefore()来添加
            }
            //创建可以显示文件的标签
        }
    }
    //firefox下检测状态改变只能用oninput,且需要用addEventListener来注册事件。 
    if (/msie/i.test(navigator.userAgent)) //ie浏览器 
    {
        document.getElementById('txt').onpropertychange = handle
    }
    else {//非ie浏览器,比如Firefox 
        document.getElementById('txt').addEventListener("input", handle, false);
        //绑定事件对象.addEventListener(事件类型,回调函数,bool值)
        //如果不传入bool值,或者为false;事件就会走冒泡阶段;反之,事件会走捕获阶段。
    }
};
</script>
</head>
<body>
<span id="msg"></span>
请输入搜索关键字
<input type="text" name="name" value="" style="width:300px;height:30px;font-size:25px; border:1px solid green" id="txt"/>百度一下
</body>
</html>

利用JSONP调用百度接口

JSONP(JSONwith Padding)是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。 该代码实现搬运自CSDN

实现原理:向输入框动态输入时关键词,将当前关键词作为问号参数后面的值,因为要跨域使用百度的接口,所以通过 JSONP 跨域创建 Ajax 请求。回调函数处理返回值。

1.使用 flex 布局实现搜索框的水平垂直居中。

2.先获取常用的 DOM 节点,避免后续频繁查询操作 DOM。

3.为了避免在输入过程中频繁发送请求(如果打字速度快),对请求函数做了函数节流,调了一下间隔 130ms 差不多正好,时间再长就会有卡顿的感觉。使用了 ES6 中的箭头函数避免了setTimeout 中 this 指向的问题。

4.在回调函数中:

  • 每一次执行时首先要清除建议框里的内容,不然上一次的结果还会存在建议框里!截取了结果中的前五个(如果把所有结果都展示出来感觉有点丑…百度官方是展示前四个搜索建议)
  • 结果处理完毕后,执行自执行匿名函数,删除创建的 script 标签;

5.由于 li 是动态创建的,点击 li 标签或者点击"搜索一下"跳转百度进行搜索时,利用事件冒泡原理,进行事件委托。这里没有考虑兼容性问题:

6.除了点击事件,键盘事件–回车键以及上下键都是进行事件委托进行注册的。最终能够实现键盘上下键鼠标选择,点击“搜索一下”或回车键实现跳转搜索。

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

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <!-- 兼容性视图 -->
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <meta content="更方便快捷搜索,从而达到事半功倍的效果" name="description">
  <title>search you want</title>
  <style>
    html {
      height: 100%;
    }
    body {
      background: #f0f3ef;
      height: 100%;
    }
    .container {
      height: 100%;
      display: flex;
      justify-content: center;
      align-items: center;
      flex-direction: column;
    }
    .bgDiv {
      box-sizing: border-box;
      width: 595px;
      height: 55px;
      position: relative;
    /* position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%); */
    }
    .search-input-text {
      border: 1px solid #b6b6b6;
      width: 495px;
      background: #fff;
      height: 33px;
      line-height: 33px;
      font-size: 18px;
      padding: 3px 0 0 7px;
    }
    .search-input-button {
      width: 90px;
      height: 38px;
      color: #fff;
      font-size: 16px;
      letter-spacing: 3px;
      background: #3385ff;
      border: .5px solid #2d78f4;
      margin-left: -5px;
      vertical-align: top;
      opacity: .9;
    }
    .search-input-button:hover {
      opacity: 1;
      box-shadow: 0 1px 1px #333;
      cursor: pointer;
    }
    .suggest {
      width: 502px;
      position: absolute;
      top: 38px;
      border: 1px solid #999;
      background: #fff;
      display: none;
    }
    .suggest ul {
      list-style: none;
      margin: 0;
      padding: 0;
    }
    .suggest ul li {
      padding: 3px;
      font-size: 17px;
      line-height: 25px;
      cursor: pointer;
    }
    .suggest ul li:hover {
      background-color: #e5e5e5
    }
  </style>
</head>

<body>
  <div class="container">
    <div class="bgDiv">
      <input type="text" class="search-input-text" value="" autofocus placeholder="关键词">
      <input type="button" value="搜索一下" class="search-input-button" id="btn">
      <div class="suggest">
        <ul id="search-result">
        </ul>
      </div>
    </div>
  </div>
  <script>
    var suggestContainer = document.getElementsByClassName("suggest")[0];
    var searchInput = document.getElementsByClassName("search-input-text")[0];
    var bgDiv = document.getElementsByClassName("bgDiv")[0];
    var searchResult = document.getElementById("search-result");

    // 清除建议框内容
    function clearContent() {
      var size = searchResult.childNodes.length;
        //childNodes方法返回数组,根据数组长度判断建议框的长度
      for (var i = size - 1; i >= 0; i--) {
        searchResult.removeChild(searchResult.childNodes[i]);
      }
    };
	// 回调函数处理返回值
    function handleSuggestion(res) {
      // 清空之前的数据!!
      clearContent();
      var result = res.s;
      // 截取前五个搜索建议项
      if (result.length > 4) {
        result = result.slice(0, 5)
      }
      for (let i = 0; i < result.length; i++) {
        // 动态创建li标签
        var liObj = document.createElement("li");
        liObj.innerHTML = result[i];
        searchResult.appendChild(liObj);
      }
      // 自执行匿名函数--删除用于跨域的script标签
      (function () {
        var s = document.querySelectorAll('script');
        for (var i = 1, len = s.length; i < len; i++) {
          document.body.removeChild(s[i]);
        }
      })()
    };

    function jumpPage() {
      window.open(`https://www.baidu.com/s?word=${encodeURI(searchInput.value)}`);
    }  
     
    var timer = null;
    // 注册输入框键盘抬起事件
    searchInput.onkeyup = function (e) {
      suggestContainer.style.display = "block";
      // 如果输入框内容为空 清除内容且无需跨域请求
      if (this.value.length === 0) {
        clearContent();
        return;
      }
      if (this.timer) {
        clearTimeout(this.timer);
      }
      if (e.keyCode !== 40 && e.keyCode !== 38) {
        // 函数节流优化
        this.timer = setTimeout(() => {
          // 创建script标签JSONP跨域
          var script = document.createElement("script");
          script.src = "https://www.baidu.com/su?&wd=" + encodeURI(this.value.trim()) +
            "&p=3&cb=handleSuggestion";
          document.body.appendChild(script);
        }, 130)
      }
    };
    // 事件委托 点击li标签或者点击搜索按钮跳转到百度搜索页面
    bgDiv.addEventListener("click", function (e) {
      if (e.target.nodeName.toLowerCase() === 'li') {
        var keywords = e.target.innerText;
        searchInput.value = keywords;
        jumpPage();
      } else if (e.target.id === 'btn') {
        jumpPage();
      }
    }, false);

    var i = 0;
    var flag = 1;

    // 事件委托 监听键盘事件
    bgDiv.addEventListener("keydown", function (e) {
      var size = searchResult.childNodes.length;
      if (e.keyCode === 13) {
        jumpPage();
      };
      // 键盘向下事件
      if (e.keyCode === 40) {
        if (flag === 0) {
          i = i + 2;
        }
        flag = 1;
        e.preventDefault();
        if (i >= size) {
          i = 0;
        }
        if (i < size) {
          searchInput.value = searchResult.childNodes[i++].innerText;
        }
      };
      // 键盘向上事件
      if (e.keyCode === 38) {
        if (flag === 1) {
          i = i - 2;
        }
        flag = 0;
        e.preventDefault();
        if (i < 0) {
          i = size - 1;
        }
        if (i > -1) {
          searchInput.value = searchResult.childNodes[i--].innerText;
        }
      };
    }, false);

    // 点击页面任何其他地方 搜索结果框消失
    document.onclick = () => clearContent()
  </script>
</body>
</html>