刚做完个小项目,正准备喘口气,产品说要添加个演示模式。
问:何为演示模式?
答:不用登录,项目仍可用,但产生的数据不上传。
问:怎么启动演示模式开关?
答:顺时针点击四角固定次数后开启。
了解需求后,心想这个简单,不就是个作弊模式嘛,一会儿就搞定。
但实现的过程中想到了一些事情,并最后有了本篇文章。
方便说明,示意图如下:
其中1到4表示相应的四个区域。html脚本示意如下:
<div data-index="1" class="tap-area">1</div>
<div data-index="2" class="tap-area">2</div>
<div data-index="3" class="tap-area">3</div>
<div data-index="4" class="tap-area">4</div>
最开始的脚本代码是:
document.querySelectorAll('.tap-area').forEach(v => v.onclick = handleClick)
let array = []
let total = 8
function handleClick() {
array.push(this.dataset.index)
if (array.length > total) {
array.shift()
}
console.log(array)
if (check(array)) {
console.log('进入演示模式')
array = []
}
}
function check(array) {
if (array.length < total) return false
return '12341234' === array.join('')
}
上面的代码很简单,为四个角添加事件,每次点击时把其代表的数字放进array数组中。并且都check数组是否已满足开启条件。
其核心是check的实现,我最开始想到的点击顺序是:用户从右上角顺时针点击两圈就可以了,即12341234就能满足需求了。
如果没有其他的想法,事情也就到此为止了。
后来想了一想,不管起点是啥,只要是顺时针即可,我该如何实现?也觉得这样比较合理一些。
首先我知道最傻的办法是:
function check(array) {
if (array.length < total) return false
let string = array.join('')
return string === '12341234' ||
string === '23412341' ||
string === '34123412' ||
string === '41234123'
}
当然,觉得太low了,改用正则:
function check(array) {
if (array.length < total) return false
return /12341234|23412341|34123412|41234123/.test(array.join(''))
}
可以优化一下:
function check(array) {
if (array.length < total) return false
return /(1234|2341|3412|4123)\1/.test(array.join(''))
}
其中正则使用了反向引用\1,表示前面括号里匹配到的内容。注意此正则不可以写成:
/(1234|2341|3412|4123){2}/
其实,它并不等价于:
/(1234){2}|(2341){2}|(3412){2}|(4123){2}/
因为前者还匹配如下的字符串:
/(1234|2341|3412|4123){2}/.test('12342341') // true
到这里,我又想到,如果需求是顺时针走三圈的话,就可以改成:
/(1234|2341|3412|4123)\1{2}/
突然脑海里进来一个恶魔,如果是两圈半呢?比如 '1234123412' 这样的数据。最开始想到的是正则:
/((12)34|(23)41|(34)12|(41)23)\1\2/
后来发现匹配其他数据,比如 '2341234123' 这个,就不可以。因为其中 \2 始终指向第二个括号里匹配的数据,即(12)。因此要匹配它,正则应该是:
/((12)34|(23)41|(34)12|(41)23)\1\3/.test('2341234123') // true
如果要把四种情况考虑进来的话,那还不如下面的这种方式来得直接呢:
/1234123412|2341234123|3412341234|4123412341/
至此我对于自己的思考还挺满意。正洋洋得意时,脑海里又进来一个恶魔,假如平板不是四个角呢?!
虽然现实的平板或者web页面都是方形的。
但真假设是5个角的话,正则改写为:
/(12345|23451|34512|45123|51234)\1/
当然,一打眼就知道肯定不是这么扩展的。此时我想到了循环链表。
12345和23451应该是同一种数据类型,只是表头不一样罢了。
难道要搞一个 helper 函数去判断 12345 和 23451 是否是同类数据?
正琢磨际,来了灵感,这种循环数据不就是无限轮播嘛。
记得有一种实现无缝滚动的方式是把dom拷贝成两份,进而滚动过程中就不用移动dom了。
12345重复3次,即:
123451234512345
是包含23451的:
1,2345123451,2345
上面的数据是目标数据,应该用正则 /2345123451/ 去匹配它,即:
正则是变的,而目标字符串是故定的。于是有了下面的写法:
function check(array) {
if (array.length < total) return false
return new RegExp(array.join('')).test('123412341234')
}
此时,为自己能想到这种方法感到十分兴奋,突然我又意识到了一个问题:这不就是查找嘛!
为啥还要用正则???
function check(array) {
if (array.length < total) return false
return '1234'.repeat(3).indexOf(array.join('')) != -1
}
也可以修改成如下:
let string = '1234'
function check(array) {
if (array.length < total) return false
return string.repeat(2 * total / string.length).indexOf(array.join('')) != -1
}
2.5圈也没问题了。
想起了一句话:当你手里拿着锤子时,看什么都像钉子。
也欢迎看《JS 正则迷你书》
本文完。