Qlock:一个简单的 JavaScript 时钟

一段简单的代码,然后就能在页面上显示时间了,好神奇:

<script>
(r=n=>setInterval(t=>{for(j=o="\n",y=5;y--;document.body['inne'
+'rHTML']="<pre>&lt"+(S="script>\n")+o+"\n\n&lt/"+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>

这是怎么做到的?

挺有意思啊

代码中间有一个数组,把每个数转换成2进制,再排列成3×5的矩阵,就对应了0~9和“:“的点阵图

挺好玩的,逐层拆解一下。

x00

代码格式化如下:

(r = (n) =>
  setInterval((t) => {
    for (
      j = o = "\n", y = 5;
      y--;
      document.body["inne" + "rHTML"] =
        "<pre>&lt" + (S = "script>\n") + o + "\n\n&lt/" + 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 循环:

  • 初始化:jo 赋值为 \n,即换行,y 赋值 5;
  • 判断:y 不为零,然后 y 自减,所以循环五次,y 的值分别为 5、4、3、2、1,但是,在循环中 y 是自减以后的值,即 4、3、2、1、0;
  • 每轮结束:页面代码(document.body.innerHTML)被赋值

解读页面代码内容:"<pre>&lt" + (S = "script>\n") + o + "\n\n&lt/" + S&lt< 符号,o\nS 被赋值为 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>&lt" + (S = "script>\n") + o + "\n\n&lt/" + S 外部每循环一次就把当前的 o 输出出来,也就是逐行(增加)输出。

x15

  • 为啥不闪,瞬间更新五次,疯了,DOM 都渲染不过来了,可以看看这个标签页的 CPU 占用。
  • 为啥显示的是完整内容,不是渲染过程中的其他形态。五次渲染,DOM 更新不过来,然后进入定时循环的间隔,终于喘口气,把最后形态更新出来,然后稍微一放松又进入了下一轮……
  • <pre> 标签的妙用:可以用 \n 换行,会默认使用等宽字体,这样字母能够上下对齐,才能呈现这样的像素效果;
  • <pre> 标签没闭合,浏览器会自动补上闭合标签,不过很多时候这个效果不太好预期,但这个输出的就这么点东西,后面没啥了,没有出问题的空间。

x16

整理一晚上,很有意思,学了不少东西,当然原理不复杂,主要是为了效果而控制代码字符的功夫,有点八股文,一种炫技。

一共四千来字,如果你读到这里,摸摸头,握个爪,鼓励一下辛苦的大老鼠呗~ 老鼠爱发电

2 个赞

除了不好认其他都挺好,主要是它炫啊。