编程有意思

精确的时间

开的坑太多了,有时候自己都忘了,现在想吐槽点什么,都不知道放在哪个帖子里好,坑王实锤。

一个问题

前端显示时间,很基础的操作,基本学到 JS 就可以做了。一般就是设置一个定时器(setInterval),看英文其实是设置一个“间隔”,实际效果是间隔指定的时间执行对应的操作。所以间隔一秒,更新一下时间,我们就能看到页面中时间的般变化了。

道理上没问题,但是如果第一次我们在 1 秒 95 的时候获取时间,读取的秒数字是 1,显示出来,但这时候基本已经到 2 秒了,可是下次更新在一秒以后,就是 2 秒 95,于是这个显示就始终偏差接近一秒。偏差不大,一般没啥影响,所以大家也都不这么着了。

当然,如果看着这个时间抢秒杀就有点不靠谱,何况这个偏移程度还不固定。不过我们也要了解,这就是个电脑本地时间,即便现在有网络对时,也不是绝对精确,所以种种偏差叠加,这点细节真的不那么重要。

但有的网页时间不显示秒,于是有人想当然的把更新间隔设置为了一分钟,这就过了,同理上面,最多可能偏差接近一分钟。

第一种方法

怎么解决呢?收缩更新间隔,比如依然一秒更新一次,就比较准确了。

继续同理,那显示秒钟的呢,也收缩更新间隔,1 毫秒更新一次,准确了——并不是!JS 做不了那么小间隔的操作,会自己对齐到一个它所能执行的最小时间间隔,比如 15 毫秒一次。

但无论如何,这个问题算是解决了吧。可是频率过高的更新是不是有点浪费系统性能呢,单纯靠暴力换精度,就不太优雅。

第二种方法

在学习 setInterval 的同时,一定也都学习了 setTimeout。这就是一个一次性的定时器。

那么获取当前时间,看一下距离下一次更新的毫秒数(相对精确),设定一个计时器(setTimeout)用来触发下一次更新,如此循环往复……

精度提高,性能也差不太多,至少比高频好太多了。然后可能就遇到回调过深的问题了……

第三种方法

要不结合一下,用 setTimeout 调整第一次的偏移,让时间对齐,然后后面就用 setInterval 好了。

第一次只要对齐了,后面间隔一样,就应该都对齐了。这挺合理的。但是现在浏览器为了节省性能,可能对后台标签进行一定程度的暂停。会不会再次导致打乱……

第四种方法

还是 setInterval,间隔一秒执行一次,但是并不是直接更新时间,而是计算一下距离下一秒还有多少毫秒,设定一个定时器(setTimeout)在那个时间更新。

这样每次都调整一下偏差,比最初的方法性能消耗大概翻倍,但用在精确度为秒(或者更大)的场景下比较合理。能够在各种场景下保证相对精确,且性能消耗也不算过分。

但是有点小问题,如果因为程序的原因导致某些延迟,那么可能导致更新推迟,于是产生跳秒现象,解决办法是稍微收缩更新间隔,比如 900 毫秒更新一次。


其实有其他方法,但是大佬们的玩法我真看不懂,就用最基础的东西初步解决一下。精度上,第四种方法的偏差控制在大约 10 ms,日常可以接受了。

我在写油猴脚本中遇到一个问题
脚本的作用是获取网站数据里的一个文件链接,然后用油猴的GM_download(url,"我脚本自己定义的文件名")下载
这对于浏览器自带的下载来说,一切正常,下载得到的文件名也是我设置的
然而有的用户使用了IDM,自动拦截,改为用IDM下载,这时,他下载得到的文件名就是IDM根据链接自动获取的文件名(这个名称不方便阅读整理),而不是我脚本提供的文件名(方便阅读整理的)。

有什么办法对脚本做出一些改进,让IDM下载也能使用我脚本提供的文件名?

ps:最简单的办法当然是让用户在IDM上对此类链接网站设置为不自动下载,不去接管

放弃吧,喵喵喵!

我试了用链接的 download 属性被拦截的话也是一样的。我的理解是拦截到了文件网址,而没有获得修改文件名的要求(前端层面)。

办法么,也许可以先把文件读回来,然后前端修改一下再触发浏览器下载(这时候其实本质是从缓存取出)。你可以看看这一篇 前端实现文件下载并重命名(兼容IE) - 大可·Duke - 博客园 (我随便搜的)。我没测试,觉得如果这种情况被下载软件拦截的话也会变得挺复杂。我记得有网站使用类似的下载方法,叫什么来着,不过能当成特色并且没多少人追随的话,相比也有一定门槛。

所以在脚本上的话,不如放弃。

1 个赞

mega

此页已读

昨天在微博看到 @小众软件 转发了一个求助:

请教一下是否一个浏览器插件可以满足:当我再次浏览某一网页链接时,提示我该网页已阅读过。谢谢! ​​​

这个问题要多简单就有多复杂,很有意思。

基本思路

对页面进行一下标记,再进入页面的时候如果有这种标记,那么这个页面已读,进行提示。

一个判断,两行指令,差别不多了。简单吧。

稍微深入

标记放在哪里?放在网页本身的相关存储里,这比较好,但是如果后期想清理所有标记,基本是做不到的,因为要打开所有相关页面才行。

油猴子本身有数据存储 API,但是如果储存上万条网址信息,就得考虑性能问题了,这可能更复杂。

所以还得用第一种方法,清理什么的就别想了,哪有那么完美的事情。何况网页自己也少不了搞一堆垃圾出来,太强迫症不好的。

然而呢

Cookies,localStorage 都是按域名存储数据的,也就是相同域名共享数据。所以只是存储已读未读(是否,布尔型)是不够的,得存储具体地址,然后跟当前地址比较。

那如果主要访问的都是某个网站,而这个网站里页面众多,于是数据量也可能惊人,

那,上数据库?!大概可以吧,但是这已经和最初简单的印象不沾边了。

还有呢

网址里面是有垃圾信息的。比如常见的 spam 参数。大家可以在桃宝随便搜索然后打开一个商品页面,会看到一个很长的网址,这个网址里只有 id 这个参数对我们是有意义的,这说明了我们打开的是哪个商品。而 spm 参数就是我说的 spam, 反正在我们看来就是垃圾信息了。而问题是这些垃圾信息是变化的,就是你明天用同样的方式找到这个商品点击进去,id 当然没变,但是 spm 变化了。于是这个网址整体发生了变化。那这个页面的网址对于浏览器来说是全新的,而对于我们则是已经阅读过了。

当然我们可以分析,可以只关注 id。但是不同网站有不同的规则,要逐个分析设定,然后区别对待。这时候复杂度就有点高了。

牺牲准确度

不追求完美实现,满分一百,我们做五十分行不行,又不是不能用。

前面的标记法应该就可以,再进一步,对于同一个域名下的地址超过一定数量自动删除早期网址。这基本上是最简单的实现方法,而且效果也还行,当然对于有 spam 的网址无能为力。

另一条路,通过链接的 visited 状态判断。给页面中所有 visited 的链接加一个参数,访问的时候如果有这个参数则提示。但是对于同一个目标地址,我从狗哥家访问过,再从百毒家搜到并不会标记上 visited,所以还是有很多漏网之鱼。如桃宝家这种加 spam 的就更加无能为力了。


折腾半天大概也就五十分,这活不好干。

可以写一个扩展,读取历史记录前100条。遍历当前页面所有a tag比对。浏览器自带的a tag变色应该就是这样实现的。

他要的似乎是访问后提示。100 条也不够。比对还是有 spam 问题。遍历 a 标签也无法覆盖 JS 跳转……

只要把问题范围放在所有网页上,想做到 80 分挺难的。

这里想要的是浏览器插件(扩展),而不是简单的脚本。

扩展本身是可以有自己独立的存储空间的,至少存个几万条网址肯定没问题。

然后扩展还可以读取历史记录、当前打开的标签页,这里也是没问题的。

最后就是怎么判断相同的URL,首先大多网站是没有追踪参数的;其次剩下大部分网站的追踪参数是通用的(utm_*);最后剩下的网站主要是一些国内大网站,我觉得10条规则就足以覆盖80%的用户需求了。

最主要的问题还是这个需求太小众了,应该不会有人为了这个需求单独写一个扩展。

嗯,可以做,就是算不上简单。可笑我最初看到这个需求窜到桌子边想两句话实现来着。

时间更新

此话题可以看作 精确的时间 的延申。

先说问题,我在做这个东西 Dashboard on desk,桌面上的信息面板 ,面板中可能有多个组件随时间更新,比如时钟、日历等。不同组件需要的时间数据有可能重合,比如都需要当前的分钟数。

显然,一个相对精确的时钟,是要起码每秒更新一次的,即便不显示秒钟,分钟数也应该准确的更新。

但是比如小时、日期、月份、年份、星期几……这些数据显然不需要每秒钟更新一次。当然如果只是这几个数据,每秒钟更新一次,也不是不能接受啦,反正都是现成的内置方法,虽然浪费性能,但也在可接受范围之内。但是像农历这种,就要自己书写一些相对复杂的函数进行计算了,还有输出月历(就是 31 天的表格那种),还有节气节日计算……这样七七八八乱七八糟的事情要是全都每秒钟计算一次,真的过分了点。

当然,现在的处理器运算速度都挺快的,这点小任务用不了几百毫秒,一秒钟的时间人家起码能休息一半的时长。可是这些运算的延迟是会影响到时间更新的速度的。前面掉了那么多头发希望时间更新更精准一点,这样一影响,又不怎么准了。

在页面加载的时候,当然要全都计算一下,毕竟最开始每一个值我们都不知道的。

下一秒,我们判断,秒钟数是不是大于等于上一次的值,如果是,说明分钟数没变化,那就不需要更新分钟数。分钟数都没变,其他就也都不需要重新计算了。这样其他数据就都变成了一分钟更新一次。

用同样的方法,还可以控制一些数据每小时更新一次,每天更新一次,每月更新一次……

但是,还有一个情况需要考虑,现在浏览器是可能对后台标签页进行暂停的,就是脚本不运行了,等标签页切换到前台再继续运行。

比如说在 23:59:30 页面被暂停了,等到 00:00:42 页面恢复了,脚本一判断, 42>30,所以分钟数没变,那小时日期啥的也全都不需要更新了,诶?!那就全错了呀!万一是跨年的时候,怕不是除了秒钟没一个数字是对的,这就很离谱。

这个情况非常极端,但不是没有可能发生。但即便把这个问题非常细致的解决好了,用户也一点都感知不到。(很多细节只有出问题了用户才会感知到。程序员的悲哀,呜呜呜

解决方法就是再加入一个标记——上次更新时间,每次运行更新函数都会记录下更新时间。正常情况下两次更新时间的间隔应该为一秒,如果这个时间大于某个值(比如设置为一分钟),那么脚本肯定是被暂停了。如果脚本暂停,情况就变得很复杂了,所以不费劲去做判断,索性把所有数据都更新一次。

这样,就能够比较好的保障数据的及时准确更新,并且对于性能消耗也做了较好的控制。然而用户看到的只是——这有个表。

2 个赞

献丑。
类似于游戏开发中的帧管理。
可以只用一个主定时器不断执行,分成分、秒、小时等不同粒度,分钟用10秒±3秒间隔来矫正,小时用10分钟±3分间隔来矫正,然后发送消息。
在需要定时更新的组件里监听不同粒度的消息。
main函数监听"visibilitychange",标签页切换为后台时保存时间戳停止定时器,切换为前台时启用定时器并且根据经过时间加上循环次数来判断是要发送哪些更新消息。

1 个赞

文字大小

这里面的组件是可以任意设定大小的,其他的倒是还好,根据情况调整一下文字大小,基本上一次计算就可以得出结果。

但是诗词这里就很复杂了。每一句诗词的长度都是不确定的,有几句也是不确定的。甚至诗词的题目长短可能都很绝,比如比内容还长得标题。

换行呢,我设置的是如果需要换行,必须在标点后面换行,这样就不会有一句诗词被截断,看着好看嘛~

但想计算合适的文字大小……你尝试列一列条件,会非常的复杂。我绞尽脑汁的想了几天,嗯,规划出了算法,然后发现运算逻辑有点复杂,如果不是这种三两句诗词的情况,那就更复杂了。

最后,最后选择了暴力测试法。我就从 1 开始尝试各个字号,直到尺寸彻底超出当前范围为止,然后选择一个元素长宽比最佳的字号,很暴力,很直接,但不会有复杂的逻辑,运算量也没有很大,都是可接受的范围。

奇怪的方向。

推荐个字体 “方正苏新诗柳楷简体”

谢谢,但是网页上不方便用字体,尤其中文字体

奇怪的发展

最近玩了一款放置类游戏,就是无聊嘛。游戏很简单,无脑点点点点……不点也可以,收入会减少。但这种游戏都有个小问题,前期点两下就各种升级,可操作内容很多;后期想升级得先点好久,或者放好久……

做个连点器,这点小事情不值一提,fooview 弄的,一轮点个一千一万次(自己设定),其实这种游戏无休无止的,完成这次升级还有下次……就打发无聊么。

不值一提。

但是这事情它占用手机啊!于是老拿着手机的习惯得到了极大的改善。……编程的好处(强行

大神啊,现在时间太少了,真想有更多时间学习编程。

根本就不是时间多少的问题,一天5分钟有没有?如果咬咬牙能有10分钟就非常好了。

问题是得坚持,还得坚持去做。就喵哈哈哈了

我公众号现在连载哄着一小口一小口喂的教程,但是看起来,唉,喂不进去。

https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MzA4MTE3OTQ0Nw==&action=getalbum&album_id=2503974890941956098&scene=173&from_msgid=2649823484&from_itemidx=1&count=3&nolastread=1#wechat_redirect

学习是一件违反人的摆烂天性的事,能做到的我们应当献上鼓励和祝福,不能做到的我们也不必去强求什么

我心态超好的

绕了个弯

经常需要把数字两位化,举个例子,时间中月份,日期,小时、分钟、秒都要保持两位数才好看,好布局。比如:2022-09-24 17:34:00 这样。

就判断一下长度(或者大小),然后决定前面是否加 0 就行了,但凡学过几天代码也都能写得出来。我也就是这么做的,甚至为此写了一个小函数,在需要的时候放进去。很方便!

如果复杂的情况,比如加 0 使得数字保持为 5 位,做法也很多,但总之需要通过几步操作才能达到目的。我今天打算整理一下代码片段,然后重写一下这个函数。然后就想,有没有什么绝妙的思路呢,搜搜吧,然后看到了……

String.prototype.padStart()

padStart() 方法用另一个字符串填充当前字符串(如果需要的话,会重复多次),以便产生的字符串达到给定的长度。从当前字符串的左侧开始填充。

所以就是根本就有原生方法去解决这个问题,而我一直自己写函数解决。绕远了。

不过也算正常吧,了解所有方法不容易啊,所以需要什么功能如果能理解其中的逻辑,大概就自己动手写了。我就常说,编程这东西学几句用几句,学的少也能做,学的多也能做,只不过写出来的代码不一样罢了。

(但我为什么没有早点知道呢……