还看到了一篇锁的文章,感觉很类似我遇到的这个问题: https://jackpordi.com/posts/locks-in-js-because-why-not
伪代码如下:
let isRefreshing = false // 标记是否正在刷新 token let requests: Array<(token: string, err?: string) => void> = [] // 需要重试的请求列表 client.interceptors.response.use((response: AxiosResponse) => { const { config, status } = response const { code } = response.data if (status >= 500) { return Promise.reject("服务器错误") } else if (code == 10003) { // access token 过期,尝试刷新 token const { refreshToken } = user.useLoginStore.getState() if (refreshToken) { // FIXME: ?? 存在并发读取 isRefreshing 为 false 导致发出多次刷新 token 的请求 if (!isRefreshing) { isRefreshing = true return refreshToken() .then(({ data }) => { const { code } = data if (code === 10000) { user.useLoginStore.setState((state) => { state.token = data.data.token }) config.headers["Authorization"] = data.data.token const retry = client(config) requests.forEach((cb) => cb(data.data.token)) requests = [] return retry } else { return Promise.reject(data.message) } }) .catch((err) => { const msg = isError(err) ? err.message : err requests.forEach((cb) => cb("", msg)) requests = [] publishInvalidTokenEvent(msg) }) .finally(() => { isRefreshing = false }) } else { return new Promise((resolve, reject) => { requests.push((token: string, err?: string) => { if (err) { reject(err) } else { config.headers["Authorization"] = token resolve(client(config)) } }) }) } } else { requests.forEach((cb) => cb("", "登录过期") requests = [] publishInvalidTokenEvent("登录过期") } } else if (code === 10000) { return response.data.data } else if (code == 10006) { // 长 token 失效 requests.forEach((cb) => cb("", "登录过期") requests = [] publishInvalidTokenEvent("登录过期") } else { return Promise.reject(response.data.message || response.data.msg) } })
]]>一个处理数字显示问题的 JS 库。
尤其是在处理有小数点的数字时会很有用。
]]><audio controls> <source src="data:audio/mpeg;base64, base64-encoded-string" type="audio/mpeg"> </audio>
重新解码作为 blob 播放,放出来就很多杂音,这中情况是哪里的问题?
<script type="text/Javascript"> const audioCOntext= new (window.AudioContext || window.webkitAudioContext)(); const decoder = new TextDecoder("utf-8"); function base64ToArrayBuffer(base64) { const binary = atob(base64); const len = binary.length; const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = binary.charCodeAt(i); } return bytes.buffer; } function playAudioChunk(base64) { const arrayBuffer = base64ToArrayBuffer(base64); audioContext.decodeAudioData(arrayBuffer).then((audioBuffer) => { const source = audioContext.createBufferSource(); source.buffer = audioBuffer; source.connect(audioContext.destination); source.start(0); }).catch((err) => { console.error("Error decoding audio data", err); }); } // ws-connection ... playAudioChunk(base64-encoded-string); </script>
]]>document.write()
方法的 Bookmarklet ,但是在这个网站中无法正常使用,具体表现总是抛出错误 Uncaught TypeError: Cannot read properties of undefined (reading 'write')
。 已尝试通过新建 iframe 获取干净的 write 方法并绑定到顶层 document ,依旧无效。问 AI 也没有提供可行的解决方案。
个人水平实在是有限,求助高手帮忙看看这网站用了什么魔法阻止使用这个方法,有解决的可能吗?
]]>比如售后如何在订单系统里找到有问题单,怎么从别的页面复制一些参数填表,给用户点一下退款这种简单的事都能问半天。
有没有技术,能实现你问 AI 如何操作 XXX ,AI 在页面上依次给你把步骤圈出来,先点 A 再输入 B 再选 C 这样。
用知识库录入操作手册感觉有局限性,因为系统界面可能改版之后导致某些入口和按钮变了,知识库跟不上
AI 能不能直接通读一遍页面结构,大致摸清楚所有地方的功能按钮怎么对应的,然后对操作使用指南进行回答?
最好直接在页面上给他圈出来,每一步点哪里,高亮爆闪一下;类似游戏上手教学关卡一样。
这种技术一般怎么搭建呀?大家有什么思路不。
]]>需求:买了个小米的摄像头,不支持 ONVIF ,但是当视频录制到 128M 就形成文件,支持存到 smb 上,想写个 web 页面方便查看。后端 express ,前端 vue3 。简单说就是播放一堆 mp4 文件。
实现:视频录制格式是 hvc1+opus 。
1 ,开始用 video 来播放没问题。copilot 帮助下很快前后端搭起来,可以根据日期选择播放视频文件。
2 ,考虑安全,前后端都加了 jwt 认证,但是 video 请求视频文件不支持带 headers.Authorization token 。
3 ,咨询 copilot ,推荐转成 blob ,这样可以了带认证了,但是 blob 必须一次把 128M 都读回来才能播放。
4 ,再咨询 copilot 如何分片,推荐了 mediasource 配合 range ,实现快速播放。
5 ,copilot 再次提示 chrome 的 mediasource 实现不支持 hvc1 ,果然 chrome 提示不支持的类型;换了 safari 的确是可以不缓冲直接播放了。
6 ,copilot 建议在后端 ffmpeg 转码以后再推出来,可是后端只是台 J1900 ,转 4k 的码比播放速度还慢。这条路也不通。
7 ,copilot 又推荐了 m3u8 的切片方法,看起来挺复杂了,感觉搞不定。
现在只有退回原点,取消认证,直接用 video 播放。看看 copilot 的帮扶道路,你说他没用吧,后面每个知识点都是现学的,但是最后还是没解决问题。
]]>apps
和 packages
目录,开发者可按规范贡献代码,采用 MIT 许可证开源,欢迎反馈共创更好的 AI 桌面工具。 ]]>问题:因为考虑到移动端输入框 输入、触碰点击和 pc 端的操作不同。例如边输入变搜索,下拉框遮挡问题...
有没有大佬有方案,感谢
]]>我正在开发一个上位机的工具,是用 tcp 进行连接的。连接完了之后我会接收到数据,格式如下:
demo:>1,2,3 demo:>5,6,7
我需要对上面的数据进行解析组成每个通道的数据(通道由每一列元素),如上所示就是有 3 个通道[1,5], [2,6], [3,7]。 然后需要实时的绘图,最大的问题是,这个连接数返回的数据很大约 1s 有 1w 行的数据量。我要怎么进行一个比较好的数据绘图呢。
]]>const ps = new Promise(function name(resolve, reject) { let i = 0; while(i<1000000){ i = i+1; console.log('i=',i); } }); console.log('promise 是异步吗?');
等上面这 100 万次循(最耗时的操作)环执行完,我还有必要通过 then 去指定回调函数吗?我直接执行回调函数就可以了,根本不需要通过 then 去执行回调函数。
]]>我去谷歌 bug 网站也看到有人提了几个了, 哪位大佬知道是啥情况不
]]>我感觉常用的也就 map 和 filter 那几个,剩下的面试官也不给提示,发现候选者回答那么几个就直接下一题了。
像 join ,pop ,push 我感觉面试官愿意提这个单词甚至就简单描述一下场景,比如我有个先进后出的栈,我想入栈和出栈,可以用什么 array 方法,候选者完全能答上来的。
感觉问点 promise 代码题都比这个好,感觉今年几个面试好像完全都不考 promise 了....
]]>之前很早有做过跟 gpt 对话的插件 也加了语音识别 但是做不到戴上耳机后的自由对话 经常会自动中断
]]>下面是 demo 代码: https://codepen.io/Asn-La/pen/RNbvYjY
]]>React 使用率高达 82% ,第不知道多少次卫冕冠军。Vue 和 Angular 并列第二,各 50%。,Svelte 26%,剩下的都很低(同一个程序员可能用过多个框架,所以总和超过 100%)。
收入方面,各个框架的 JS 程序员平均年薪在 7 万美元。唯独 Vue 程序员最低,只有 5 万美元。
还有其他很多的结果,比如全栈框架、后端、打包工具、跨平台工具等等,感兴趣可以看看 https://2024.stateofjs.com/ 。以后技术选型也有参考。
]]>一个具有较高可扩展性的 JS AMD 规范模块加载器 partic2-iamdee
Github
Gitee
在上面的基础上做了个早期形态的“全栈”包管理 pxseed ,并附带一个 Javascript Notebook 。
Github
Gitee
纯网页模式 Javascript Notebook 演示地址
Github Pages
之前 V 站大佬推广的平台 帽子云
在知道 Typescript 之前不习惯写 JS 的,然后接触到 Typescript 的时候第一个问题就是模块化的问题,当时比较多的模块化方案就是 CommonJS 和 AMD 了,为了在浏览器上用自然用的是 AMD 。虽然有 webpack 了但是 AMD 更简单,然后一直用到了现在。
到了现在已经出现了很多前端构建工具,但遗憾的是,Javascript 还是没有一个扩展性较强的统一的模块机制。
例如 Node ,早期用的 CommonJS ,现在开始转变为 ES Module ,浏览器都支持了 ES Module ,但是各种打包工具又会在打包后使用不同的加载方式。而且还有个特例-ServiceWorker 中不支持 ES Module 中的动态 import 。
同时,可能是照顾到打包工具等实现,ES Module 的可扩展性非常弱,可能只有 Import maps 改变映射这种级别的扩展。例如我想从内存/IndexedDB 中加载模块,ES Module 就不能支持。
同时我也接触了 Jupyter Notebook/Lab ,觉得是个很方便的平台,可以动态的执行 Python 并查看每一步的结果。于是也想在 JS 上实现个类似的东西,虽然有 IJavascript ,但是我想有能不依赖后台 python 的这么一个东西。
然后就有了做包管理的想法。
NPM 分发和获取的经常是编译产物。可以对比下 Golang 。Golang 虽然槽点不少,但包管理工具链还是比较优秀的。分发的是源码,对某个函数有疑问,可以在 IDE 中跟进源码,NPM 上则大多只能跟到类型声明。
Node/NPM 的设计对比 python/PIP 有一些地方对 Notebook 不太友好,PIP 默认装全局,NPM 默认装项目文件夹,当然 NPM 的设计一定程度解决了版本问题,不过我的观点是版本问题还是需要第三方库本身做好,包管理来处理兼容问题多少会有一些难以解决的 corner case 。
因此我尝试做了这个 pxseed“全栈”包管理,在这个框架下,前端浏览器和后端 Node 都可以用一套编译产物,并用 AMD 加载模块,Typescript 源码直接分发和下载,这样 IDE 里点一下可以跟进到源码。为了兼容 NPM 生态,pxseed 也添加了 Rollup 来打包 NPM 模块为 AMD 供前端使用。
当然 AMD 做前端是有致命缺点的,AMD 加载零碎文件的性能差,没有 Tree-Shaking ,没有 SEO 。但如果是想做成个类似 python 的属于 JS 的全栈包管理本地用,还是挺不错的。而且 partic2-iamdee 高度可定制化,也让优化上述问题成为可能,比如,你也可以用 Websocket 来加载 JS 模块。
当然这些东西都有点"路子野",说不定已经早有人做过更好更成熟的实现方案,如果有的话欢迎推荐给我。
]]>console.log('Hello')
输出 echo Hello
]]>video = document.querySelector("#vidplayer > video")
大概像这样。但是发现这个网站很神奇的一点是,如果打开新网页直接在控制台输入这句命令,是获取不到 video 的,必须先经过审查元素的这一步,才能在控制台里获取到。
体感上这像是只有进入审查元素了,才会动态生成相关元素,但是进入之前也能使用播放功能,感觉非常神奇。请问有没有老哥懂得这是什么原因呢
对 js 接触得很浅,可能是很粗浅的问题,希望大家不吝赐教
]]>我的工作内容有写网页游戏,游戏涉及到前后端通信,没找到顺手的通信包,所以手写了一个通用的 js 通信包:remote。
js
子集iframe
/ Java
服务器 等 // 远端 remote.register('something', async (params: Whatever) => { return WhatYouWant }) // 本地 // res === WhatYouWant const res = await remote._.something(xxx)
由于通信双方是平等的,所以 B 调用 A 的流程也是一样的
]]>
def cid_hash_file(path): h = hashlib.sha1() size = os.path.getsize(path) with open(path, 'rb') as stream: if size < 0xF000: h.update(stream.read()) else: h.update(stream.read(0x5000)) stream.seek(int(size/3)) h.update(stream.read(0x5000)) stream.seek(size-0x5000) h.update(stream.read(0x5000)) return h.hexdigest().upper()
上面的代码用 chatGPT 转换后是这样的
function cidHashFile(path) { const h = crypto.createHash('sha1'); const size = fs.statSync(path).size; const stream = fs.createReadStream(path); if (size < 0xF000) { stream.on('data', (chunk) => { h.update(chunk); }); } else { stream.on('data', (chunk) => { if (stream.bytesRead <= 0x5000) { h.update(chunk); } else if (stream.bytesRead >= Math.floor(size / 3) && stream.bytesRead < Math.floor(size / 3) + 0x5000) { h.update(chunk); } else if (stream.bytesRead >= size - 0x5000) { h.update(chunk); } }); } stream.on('end', () => { const result = h.digest('hex').toUpperCase(); console.log(result); }); stream.on('error', (err) => { console.error('File reading error:', err); }); }
试了不同文件计算出来的 hash 不一致,有没有大佬知道原因的?
]]>昨天突发奇想,想到另一个方案,这个方案我测试在 node 和 chrome 上是可行的。 就是借助 new Function 自定义一个带标记的函数,就可以将标记的函数名插入到 new Error().stack ,就可以区分当前的任务栈,实现 task local,继而也可以通过 hook promise 实现 async 函数中止。虽然和最早方案的表现有一些差异。这个方案经过测试确实是可行的。
但是这个方案只在 V8 测试是生效的,在 txiki.js(quickjs)上 await 的堆栈会丢失。并且在获取当前 task 时有较高的性能损耗,所以只能说仅供参考,没什么实际应用价值
]]>项目使用腾讯云点播服务进行视频内容分发。需要实现前端视频流量监控功能,当用户观看视频消耗的流量达到特定阈值时,自动终止视频播放。同时会将相关数据记录到数据库,为统计页面做准备。
performance.getEntriesByType('resource')
获取视频资源的 transferSize
transferSize
始终返回 0 ,无法获取实际流量数据但是目前的实现方式是 根据 video 播放的进度,找到对应的词条的 dom 高度,向上调整若干 offsetTop ,挺原始的方式
油管字幕内容是一个类似 xml 格式的文件,从内容上看,应该是把 script 直接丢到页面(或者某个容器)里头,没想明白这种方式是如何实现的。 文件大概长这样
]]>正常流程大致是: 审批人打开页面,iframe 中的表单会填充名称,页面上点击审核完成的按钮时,提交表单后台保存,然后进行后续流程处理。firefox 下上述流程正常。
现在的问题是: 奇安信 chrome 内核浏览器下,提交表单的这步被略过,直接执行了后续流程处理。
提交表单的方式是:(尝试了 js 和 jquery 都有问题)
$(window.frames['tabcont'].document).find('form').submit();
尝试过将提交表单之后的逻辑代码去掉,就又能正常。
想请教下有没有改动比较小的修法,先谢谢各位大佬了。
]]>我开发了一个 Tampermonkey 脚本,但是往往需要手动刷新页面才能触发。请教该如何优化。(代码附在最后面)
inSpirehep.net这个网站可以复制文献的BibTeX
引文信息(通过点击文献左下角的cite
实现),现在我想在BibTeX
内容中加入一个inSpirehep.net网站的编号。为此我使用 Tampermonkey 脚本在文献的右下角添加了一个Copy BibTeX to Clipboard
的按钮,点击按钮可以将加入编号的BibTeX
内容复制到系统剪贴板。
现在的问题是,这个脚本往往需要重新刷新页面才能触发,在第一次展示文献列表的时候不会出现。不知道该如何修复。PS ,这份代码也是在 ChatGPT 协助下完成的,本人完全没有 JS 开发经验。希望能有高手指点一二。
// ==UserScript== // @name Copy BibTeX to Clipboard-v2 // @version 2.0 // @description Adds a button to fetch data and copy it to clipboard // @author Chipmunker // @match https://inspirehep.net/literature* // @run-at document-end // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @grant GM_notification // ==/UserScript== (function () { 'use strict'; var maxAttempts = 200; // 最大尝试次数 var attempts = 0; // 当前尝试次数 var copyBtnText = "Copy BibTeX to Clipboard"; var copyBtnColor = "#000008"; var copiedBtnText = "Data Copied to Clipboard"; var copiedBtnColor = "#f44336"; var showDuration = 5000; // ms var regexPattern = /^https:\/\/inspirehep.net\/literature\/(\d+)$/; // 按钮的点击事件处理函数 function CopyToClipOnClick(button, inspireID) { // 发送 GET 请求 GM_xmlhttpRequest({ method: 'GET', url: 'https://inspirehep.net/api/literature/' + inspireID, // 替换为你要请求的 API URL headers: { 'authority': 'inspirehep.net', 'accept': 'application/x-bibtex' }, onload: function (response) { // 获取响应数据 var data = response.responseText; // console.log("|" + data + "|"); // 为 BibTeX 添加 inspireID let BibInspireID = data.replace(/\n\}\n$/, ",\n inspirehepID = \"" + inspireID + "\"\n}\n"); // 将数据写入系统剪贴板 GM_setClipboard(BibInspireID); // 修改复制按钮并在一秒后修复 var textSpan = button.querySelector('.v-top, #span-' + inspireID); textSpan.innerText = copiedBtnText; textSpan.style.color = copiedBtnColor; setTimeout(function () { textSpan.innerText = copyBtnText; textSpan.style.color = copyBtnColor; }, showDuration); // 提示用户数据已复制 // alert('Data copied to clipboard: \n' + BibInspireID); GM_notification({ text: BibInspireID, title: copiedBtnText, // url: 'https:/example.com/', onclick: (event) => { // The userscript is still running, so don't open example.com // event.preventDefault(); // Display an alert message instead // alert('I was clicked!') console.log('NotificationClick') } }); } }); } function findRefSearchBtnAll() { var intervalId = setInterval(function () { // 查找 ref Search button var refSearchBtnAll = document.querySelectorAll('[data-test-id="reference-search-button"]'); if (refSearchBtnAll.length > 0) { // 找到了 ref Search button 元素,可以执行相应的操作 clearInterval(intervalId); // 停止轮询 console.log('找到了 ref Search button 元素'); var Btn = document.querySelector('.CopyBtn'); if (Btn) { return; } for (var refSearchBtn of refSearchBtnAll) { var button = createCopyBtn(refSearchBtn, copyBtnText); // 按钮点击事件处理函数 button.addEventListener('click', function () { var inspireID = this.id; CopyToClipOnClick(this, inspireID); }); } } else { attempts++; // 增加尝试次数 // console.log('未找到 ref Search button 元素'); if (attempts >= maxAttempts) { clearInterval(intervalId); // 达到最大尝试次数,停止轮询 console.log('未找到 ref Search button 元素, 达到最大尝试次数'); } } }, 100); // 每隔 100 毫秒钟检查一次 } const observer = new MutationObserver(function (mutationsList, observer) { // 在 div 变化时重新运行脚本的代码 // 例如,重新加载页面 // location.reload(); findRefSearchBtnAll(); }); var currentURL = window.location.href; if (regexPattern.test(currentURL)) { findRefSearchBtnAll(); } else { findRefSearchBtnAll(); // 监听目标 div 的变化 const targetDiv = document.querySelector('[class="ant-col ant-col-xs-24 ant-col-lg-17"]');//'[data-test-id="search-results"]'); if (targetDiv) { observer.observe(targetDiv, { attributes: false, childList: true, subtree: true }); } } })(); function createCopyBtn(refSearchBtn, copyBtnText) { var refSearchSpan = refSearchBtn.parentNode; // 构造 copy icon var copyImgSpan = document.createElement('span'); copyImgSpan.role = "img"; copyImgSpan.innerHTML = '<svg viewBox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"></path></svg>'; // 构造 icon span var icOnSpan= document.createElement('span'); iconSpan.className = "icon"; iconSpan.appendChild(copyImgSpan); // 获取文献 ID var inspireMatch = refSearchBtn.href.match(/citedby:recid:(\d+)$/); var inspireID = inspireMatch[1]; // 构造 __IconText__ var IcOnTextSpan= document.createElement("span"); IconTextSpan.className = "__IconText__"; var textSpan = document.createElement("span"); textSpan.className = "v-top"; textSpan.innerText = copyBtnText; // "Copy BibTeX to Clipboard"; textSpan.id = "span-" + inspireID; IconTextSpan.appendChild(iconSpan); IconTextSpan.appendChild(textSpan); // 构造 Button var button = document.createElement('button'); button.type = "button"; button.id = inspireID; button.className = "CopyBtn"; button.appendChild(IconTextSpan); var UserActiOnSpan= document.createElement("span"); UserActionSpan.className = "__UserAction__"; UserActionSpan.appendChild(button); refSearchSpan.parentElement.insertBefore(UserActionSpan, refSearchSpan); return button; }
]]>