0%

正则面试题汇总

前言

正则表达式是匹配模式,要么匹配字符,要么匹配位置。关于正则表达式的原理和写法,网上的文章已经特别之多了,本篇文章将不着重于原理与方法,将直接以各种题目的形式来进行正则表达式的介绍,同样,题目的难度也将由弱至强。首先附上经典的正则链接网站: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位或者六位

1
2
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可以省略也可带着

1
2
3
4
5
6
7
8
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地址

1
2
3
4
5
6
7
8
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;分隔符有三种可用,且要求分隔符前后使用一样。

1
2
3
4
5
6
7
8
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,以下的代码可以实现

1
2
3
4
5
6
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代表第1、2、3个分组

字符串去重

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

1
2
3
4
let regex = /(\w)\1+/g;
//g代表全局匹配,会在一次匹配结束后继续匹配多次。而非仅匹配一次
let str = "aaaaabbbbbddddggggggggffff";
let res = str.replace(regex, "$1");

HTML标签

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

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

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

驼峰字符串

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

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

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

邮箱格式

1.不限制长度

2.不限制大小写

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

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

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

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

识别十进制整数

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

eg:’12px’、’0x12’

1
2
3
4
5
6
7
8
9
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. 不足两位补零
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

1
2
3
4
5
6
7
8
9
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. 如果存在多个同名参数,则返回数组

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*  获取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进行替换

1
2
3
4
5
6
7
8
9
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方法

1
2
3
4
5
6
7
8
9
10
11
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为:”http://www.xiyanghui.com/product/list?id=123456&sort=discount#title";parseUrl(url)返回的结果为object如下:

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
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、要求匹配的位置不能是开头,因此用(?!^)

1
2
3
4
let regex = /(?!^)(?=(\d{3})+$)/g;
let regex = /(?!^)(?=(\d{3})+$)/g;//先要求匹配的位置不能是开头,$代表从单词末尾开始匹配,/g代表
let string = "123456789";
let res = string.replace(regex, ',');

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

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

密码判断

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

1
2
3
4
5
6
7
8
9
10
11
12
13
//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就能解决的问题,使用正则会提高时间负杂度。

提取年月日

1
2
3
4
5
6
7
8
9
10
11
12
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。

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
6
7
8
9
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。否则,将返回一个数组。

1
2
3
4
5
6
7
8
9
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方法

1
2
3
4
5
6
7
8
9
10
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实现,就不用上述定义的函数了,而是用更简单的方式。

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

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
<!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>
-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!