Qingwa
(青小蛙)
1
一段简单的代码,然后就能在页面上显示时间了,好神奇:
<script>
(r=n=>setInterval(t=>{for(j=o="\n",y=5;y--;document.body['inne'
+'rHTML']="<pre><"+(S="script>\n")+o+"\n\n</"+S)for(x=-001;
x++<63;o+=`(r=${r})()`[j++].fontcolor(c?'#FF0':"#444"))c=x/2%4<
3&&[31599,19812,14479,31207,23524,29411,29679,30866,31727,31719
,1040][(D=Date()[16+(x/8|0)])<10?D:10]&1<<(x/2|0)%4+3*y},100))()
</script>
xml123
(xml123)
4
代码中间有一个数组,把每个数转换成2进制,再排列成3×5的矩阵,就对应了0~9和“:“的点阵图
dms
(稻米鼠)
5
挺好玩的,逐层拆解一下。
x00
代码格式化如下:
(r = (n) =>
setInterval((t) => {
for (
j = o = "\n", y = 5;
y--;
document.body["inne" + "rHTML"] =
"<pre><" + (S = "script>\n") + o + "\n\n</" + S
)
for (
x = -001;
x++ < 63;
o += `(r=${r})()`[j++].fontcolor(c ? "#FF0" : "#444")
)
c =
(x / 2) % 4 < 3 &&
[
31599, 19812, 14479, 31207, 23524, 29411, 29679, 30866, 31727,
31719, 1040,
][(D = Date()[16 + ((x / 8) | 0)]) < 10 ? D : 10] &
(1 << ((((x / 2) | 0) % 4) + 3 * y));
}, 100))();
x01
最外面即时执行函数:(r = (n)=>
)();,实际无运行参数,这个 n
未被使用,内部直接一个函数,所以这里的箭头函数连大括号都没有。
x02
定时循环 setInterval((t) => {}, 100)
,100ms 执行一次,t
同样未被使用。
x03
内部两层 for 循环,先说明一下,for 循环的小括号里面是三个语句:
- 第一句:最开始执行一次,视为初始化;
- 第二句:每轮开始前执行,获得一个布尔型结果,决定是否继续循环;
- 第三局:每轮结束后执行。
x04
第一层 for 循环:
- 初始化:
j
、o
赋值为 \n
,即换行,y
赋值 5;
- 判断:
y
不为零,然后 y
自减,所以循环五次,y
的值分别为 5、4、3、2、1,但是,在循环中 y
是自减以后的值,即 4、3、2、1、0;
- 每轮结束:页面代码(
document.body.innerHTML
)被赋值
解读页面代码内容:"<pre><" + (S = "script>\n") + o + "\n\n</" + S
,<
是 <
符号,o
是 \n
,S
被赋值为 script>\n
,这里的重点是赋值的同时也会输出该值。所以其实是:"<pre><script>\n" + o + "\n\n</script>\n"
x05
内部 for 循环:
- 初始化:
x = -001
,就是 x = -1
;
- 判断:
x
小于 63,并自增,也就是 -1 到 62,共计 64 次;
- 每轮结束:```(r=${r})()`[j++].fontcolor(c ? “#FF0” : “#444”)`` ,比较复杂,解读如下:
x06
r
是什么?回到最开头,它被赋值为了即时执行函数内部的主函数。因为赋值同时返回该值,所以,这个函数被执行了。所以 r
变量现在指向一个函数。
- 如果把函数对象以字符串的形式输出,则输出其源代码。
- 所以开始的模板字符串就是把这个函数的内容代入,补充缺失部分,获得这个脚本的完整代码。
x07
j
是什么?是 \n
;
j++
是自增运算,自增只能对数字进行运算,但 j
是个字符串;
- JS 是弱类型语言,在类型不匹配时会自行尝试转换;
- 字符串转换为数字,如果不能从字面意义转换为数字,即用
isNaN
判断为 true
,则会转换为 0;
- 所以就是逐个遍历代码中的字符,
j
在初始化之后就只有此处使用,所以一直在自增,外层循环 5 次,内层循环 64 次,代码一共五行,每行 64 个字符,正好完全遍历;
- 所以为什么有些地方的写法很奇怪呢,就是为了控制字数。
x08
.fontcolor(c ? "#FF0" : "#444")
是根据变量 c
设置字符颜色;
fontcolor
方法很古老,已经不推荐使用了,但是这比现在使用属性或者样式定义可便捷多了,产生的效果如:<font color="#444">(</font>
x09
问题来了——c
这个变量前面没有。因为它是在循环内部赋值的。而刚才我们分析的这个语句是在每轮循环结束之后才执行,即 c
变量被赋值之后才执行。
x10
第二层循环内部就解决一个问题:c
是多少。我们先看 (x / 2) % 4 < 3
x
代表了当前是某一行中的第几个字母(y
代表第几行);
- 显示的时间是
00:00:00
格式;
- 用两个字母作为一个像素显示,所以实际是 32 * 5 点阵屏,但并不是 4 * 5 像素代表一个数字,而是 3 * 5 像素,这样留出最右侧像素作为数字之间的间隔;
- 如此,获取当前是字母中的第几个像素,如果是前三个,那么参与显示,就要进行后续判断,否则是用来间隔的像素,肯定显示灰色;
- 所以如果前面计算结果等于 3,则为间隔像素,则此判断不成立,不会执行
&&
之后的内容,则 c
的值为 false
。
x11
-
[ 31599, 19812, 14479, 31207, 23524, 29411, 29679, 30866, 31727, 31719, 1040 ]
数组记录了表示时间的每个字符的格式,11 个,因为有 :
号,显然,最后那个比较短的是冒号的信息;
-
Date()
获取的时间是 Thu May 30 2024 20:17:41 GMT+0800 (中国标准时间)
形式的,去掉前 16 个字符就是时间(20:17:41
)部分了;
-
x/8
表示取时间中的第几位,但是此时可能有小数,用 |0
向下取整(按位或会省略小数部分,并保持整数部分不变);
-
取出的值如果小于 10(0~9),则为数字,否则为 10(数组中编号为 10 的是第 11 项);
-
细节在于 ':'
和数字比较大小,无论是何种关系(大于、小于、等于、大于等于、小于等于)都会返回 false
。
x12
接下来看 (1 << ((((x / 2) | 0) % 4) + 3 * y));
(((x / 2) | 0) % 4)
当前字符是组成某个时间数字的像素中的横向第几个像素,此处可能的计算结果为 :0、1、2,没有 3 是因为此值已经被过滤掉了(间隔像素);
- 加上
3 * y
,y 代表行数,但是是倒序(4、3、2、1、0),此处可能的计算结果为:12、9、6、3、0;
- 两者相加,可能的计算结果为: 0~14,共计 15 个,即 3 * 5 个像素;
- 将 1 左移 n 位,即在二进制的从右往左第 1~15 的某一位是 1。
x13
上述结果和数组中选出当前数字的编码信息进行按位与(&
)。也就是数组中的数字的二进制形式中某个位置是 1 就代表这个像素高亮。这些数字的二进制最长为 15 位,即 15 个像素。
x14
颜色有了,但好像没输出出来?!翻回头再看两个循环中小括号里的第三句:
o +=
内部每循环一次,变量 o
增加一个设置好颜色字符;
document.body["inne" + "rHTML"] = "<pre><" + (S = "script>\n") + o + "\n\n</" + S
外部每循环一次就把当前的 o 输出出来,也就是逐行(增加)输出。
x15
- 为啥不闪,瞬间更新五次,疯了,DOM 都渲染不过来了,可以看看这个标签页的 CPU 占用。
- 为啥显示的是完整内容,不是渲染过程中的其他形态。五次渲染,DOM 更新不过来,然后进入定时循环的间隔,终于喘口气,把最后形态更新出来,然后稍微一放松又进入了下一轮……
<pre>
标签的妙用:可以用 \n
换行,会默认使用等宽字体,这样字母能够上下对齐,才能呈现这样的像素效果;
<pre>
标签没闭合,浏览器会自动补上闭合标签,不过很多时候这个效果不太好预期,但这个输出的就这么点东西,后面没啥了,没有出问题的空间。
x16
整理一晚上,很有意思,学了不少东西,当然原理不复杂,主要是为了效果而控制代码字符的功夫,有点八股文,一种炫技。
一共四千来字,如果你读到这里,摸摸头,握个爪,鼓励一下辛苦的大老鼠呗~ 老鼠爱发电
2 个赞