一次让我重新理解 touchmove 的线上 Debug
一个 AI Chat 页面在模型流式回答时,用户按住屏幕滑不动。我以为是 CSS 的事,结果一路追到了 W3C 规范。 项目地址:mosuzi/touchmove-debug 先把场景画出来 我们做的是一个移动端的 AI Chat。用户问一个问题,模型流式吐 token,回答区一边刷新一边长大。所有人都熟悉这个交互。 某天 QA 提了一个让人皱眉的 bug: iPhone 上,当模型正在回答时,手指按在回答区里上下滑动,页面纹丝不动。等回答结束,或者把手指挪到回答区以外,滚动又正常了。 第一反应:什么离奇 bug?我打开同一个页面,发完消息开始疯狂滑屏,竟然真的一动不动。手指像吸在了一块橡皮泥上。 模型答完,舒一口气,再滑,丝滑。 这个项目就是把当时的现场用最小代码复刻出来了,配套一个浮窗探针,可以亲自看 touch 事件被吞的瞬间。运行 pnpm install && pnpm dev 即可复现。 错误的方向 #1:CSS 最先怀疑 CSS。理由很合理:移动端“滑不动”九成是 touch-action、overflow、overscroll-behavior ...
给 AI 流式回答装一台打字机:一次自适应节奏的工程实践
一个可交互的 React Hook playground:把一段经久不衰的打字机算法剥离出业务依赖,配上 SSE 模拟器与组件库式 demo,让“自适应节奏”看得见。 项目地址:mosuzi/typewriter 起因 最近在研究 AI 流式对话场景下的打字机实现,翻到一段在生产环境跑了很久的算法,越看越觉得里面藏了不少与“流式 + 视觉节奏”相关的思考。可惜原始代码里夹杂着 deepmerge、randomChars、useLastData 这些非核心的东西,把真正有趣的算法藏得很深。 于是我把它抽成一个独立 playground,剥掉所有业务杂质,再配一个 SSE 模拟器和一个仿组件库 demo 风格的聊天室页面,方便随时调参看波形。下面是这次重构和演示项目的一些笔记。 为什么需要打字机? LLM 推理是 token-by-token 推送的,但底层 SSE 流的节奏其实非常糟糕: 大爆发:模型在 prefill 完成、KV cache 命中或者并发降下来时,会一次性吐出几百字。 慢空窗:网络抖动、tokenizer 拆词、服务端 batching 都可能把下一段延迟 300 ...
Generator 简介
Generator 定义 Generator 是隐藏类 Iterator 的子类,Generator 对象是一个 JavaScript 的标准内置对象,它由生成器函数返回并且它符合可迭代协议和迭代器协议 生成器函数 形如 function* name() {},它内部可以包含 yield 表达式,具体功能下文再述 可迭代协议:简单说就是一个对象实现了 [Symbol.iterator]() 方法,这个方法无参,返回一个符合迭代器协议的对象 迭代器协议:定义了产生一系列值(无论是有限个还是无限个)的标准方式,当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。最简的迭代器对象必须实现 next() 方法,该方法无参或接受一个参数,返回一个符合 IteratorResult 接口的对象。IteratorResult 简单地描述一下就是: 1234interface IteratorResult { done?: boolean; value?: any;} 其中 done 标识是否完成了迭代 写一个简单的符合可迭代协议和迭代器协议的对 ...
一步一步手写 Promise
一步一步手写 Promise 根据 Promises/A+ 规范实现 Promise Promise/A+ 规范 Promises/A+ 规范(以下简称规范)可以在 Promises/A+ 查看,需要中文可以选择 AI 翻译或者 Promise/A+ 规范 简单来说,符合 Promises/A+ 规范的 promise(以下简称 promise )表示异步操作的最终结果,其应该是一个具有 then 方法的对象或者函数,该 promise 的 then 方法的行为符合规范。promise 必须处于 pending、fulfilled、rejected 三种状态之一,且其状态只允许由 pending 转换为其他两种状态——即其他两种状态不允许改变。当处于 fulfilled 状态时,promise 具有一个不可修改的值,表示异步处理的结果值;当处于 rejected 状态时,promise 具有一个不可修改的拒因(reason),表示异步处理被拒绝(或者说失败)的原因 构造 Promise 类基本骨架增加状态、最终值和拒因回调方法实现修改 promise 状态的方法按照 ES6 中使用 ...
告许仙词
告许仙词 皓皓白鳞,可曾识,伊人模样。漫漫洪水,岂可知,情意绵长。佛法无边,佛不言,执念为何。人间冷暖,人总叹,轮回由我。浮生自非空度日,莫负卿卿相伴老。玉簪绿萧是信物,来生在世认今朝。金山有令金光落,灵魄尚存灵阻魔。纵使雷峰塔下镇,拾阶扫尘心未隔。
JS 定时器拾遗
JS 定时器拾遗 今天看 JS 定时器定义的时候,发现了两个被我遗漏的点: HTML 标准 提到,5 层定时器嵌套后最小间隔不能小于 4ms Timers can be nested; after five such nested timers, however, the interval is forced to be at least four milliseconds. setTimeout() 方法不只接收两个参数,而是从第三个参数开始的所有其他参数都会被传递给定时器会执行的函数,参见 setTimeout MDN 12345678setTimeout(code)setTimeout(code, delay)setTimeout(functionRef)setTimeout(functionRef, delay)setTimeout(functionRef, delay, param1)setTimeout(functionRef, delay, param1, param2)setTimeout(functionRef, delay, param1, param2 ...
JS 并发调度器
JS 并发调度器 浏览器的并发连接数量是有限的,对于 chrome,一般是 6 个。那么对于瞬时大量请求发送的场景,如果不加管控,那么有些排队靠后的请求,很可能在等待到浏览器调度前,就耗尽自己的时间限制,从而自我取消。针对这个问题,可以设计实现一个并发调度器,将并发请求“缓存”起来,控制并发数,从而避免请求在浏览器排队时超时 代码如下: 1234567891011121314151617181920212223242526272829const LimitConcurrentScheduler = function (concurrent = 5) { this.concurrent = concurrent; this.pendingRequests = 0; this.queue = [];};LimitConcurrentScheduler.prototype.dequeue = function () { while (this.pendingRequests < this.concurrent && this.que ...
基于acme的自动重新获取ssl证书脚本的修正脚本
基于acme的自动重新获取ssl证书脚本的修正脚本 acme 是一个很棒的 let’s encrypt 的客户端 但是,当本地的 nginx 设置了强制跳转 https 时,脚本会在验证域名的时候失败。为解决这样的问题,我写了一个新的脚本修正这个问题 123456789101112131415161718list="example.com another.example.com"for i in $list; do mv "/etc/nginx/conf.d/"$i".conf" "/etc/nginx/conf.d/"$i".conf.bak";cp "/etc/nginx/conf.d/"$i".conf.pre" "/etc/nginx/conf.d/"$i".conf";doneservice nginx reload/root/.acme.sh/acme.sh --cron --home &q ...
53.最大子数组和
53.最大子数组和 题目 给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组是数组中的一个连续部分。 示例 1: 输入: nums = [-2,1,-3,4,-1,2,1,-5,4] 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6 。 示例 2: 输入: nums = [1] 输出: 1 示例 3: 输入: nums = [5,4,-1,7,8] 输出: 23 提示: 1 <= nums.length <= 105 -104 <= nums[i] <= 104 **进阶:**如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的 分治法 求解。 思路 实话说,我费了老大的功夫最后也没能通过所有的用例,所以在此记录一下。 错误解法 一开始我想到的是,通过两端删减元素的方法,一步步算出中间遇到的所有可能产生更大和的数组,从而完成题解。讲道理这个思路理论上可行,但是实际执行起来有很多问题。 首先,当数组的首尾两端存在一个更小的负值的时候,删除它会让数组的和变大 ...
40.组合总和 II
40 组合总和 II 题目 给定一个候选人编号的集合 candidates 和一个目标数 target,找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用 一次。 注意: 解集不能包含重复的组合。 示例 1: 输入: candidates = [10,1,2,7,6,1,5], target = 8, 输出: [ [1,1,6], [1,2,5], [1,7], [2,6] ] 示例 2: 输入: candidates = [2,5,2,1,2], target = 5, 输出: [ [1,2,2], [5] ] 提示: 1 <= candidates.length <= 100 1 <= candidates[i] <= 50 1 <= target <= 30 思路 这一题很像三数之和的一般化。三数之和,可以通过双指针完成,但这里的一个有效解的长度是未知的,意味着并不能固定指针个数,而要动态增添指针。 第一眼的思路 我第一遍写,与前一 ...
31.下一个排列
31. 下一个排列 题目 整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。 例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。 整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。 例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。 类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。 而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。 给你一个整数数组 nums ,找出 nums 的下一个排列。 必须 原地 修改,只允许使用额外常数空间。 示例 1: 输入: nums = [1,2,3] 输出: [1,3,2] 示例 2: 输入: nums = [3 ...
华胥记谈:农家楼馆杀人事件
在一个寂静的夜晚,整个小镇突然陷入了一片黑暗,停电让一切都变得格外诡异。项清、秦晴以及其他众人正身处一乡间小酒馆中,他们在这突如其来的黑暗中显得惊慌失措。项清紧紧拉着秦晴的手,试图在黑暗中找到一丝安全感,众人的目光中都充满了惶恐和不安。 这时,三楼突然传来一阵令人心悸的塌陷声,在寂静的环境中显得格外清晰。这突如其来的声响让众人的神经瞬间紧绷到了极点,大家面面相觑,不知道接下来会发生什么可怕的事情。而在这慌乱之中,都奕却表现得异常冷静,他的眼神中似乎隐藏着什么不为人知的秘密,这一异常的表现引起了项清的怀疑。 项清心中暗自揣测着都奕的异常表现,而此时大家在恐惧的驱使下,开始摸索着往楼梯口的方向走去。 当他们终于艰难地到达楼梯口时,一股浓烈的血腥气息扑面而来。众人借着微弱的月光,只见眼前是一个满是鲜血的房间,地上横七竖八地躺着一些尸体,还有一条血淋淋的大腿,场景极其恐怖。 都奕看着这一切,淡淡地说了一句:“这些尸体似乎不像是今天刚死的。”他的这句话让项清心中的怀疑更加强烈了,他觉得都奕一定知道些什么。 大家都被这恐怖的场景吓得不轻,有人甚至忍不住呕吐起来。而就在这时,不远处又传来一阵轻微的响 ...