JS 动态注入 CSS 样式:setStyle 函数
在油猴脚本或 Chrome 扩展里需要动态修改页面样式时,直接写 element.style.xxx 只能改行内样式,无法用 CSS 选择器批量设置。更好的方案是动态创建 <style> 标签。 setStyle 函数 function setStyle(id, css) { let style = document.getElementById(id); if (!style) { style = document.createElement("style"); style.id = id; document.head.appendChild(style); } style.textContent = css; }用 id 做唯一标识——如果该 id 的 <style> 已存在就直接更新内容,不会重复插入。 使用示例 // 隐藏广告 setStyle("ad-blocker", ` .ad-banner, .sidebar-ad, [class*="advertisement"] { display: none !important; } `);// 强制暗色模式 setStyle("dark-mode", ` body { background: #1a1a1a !important; color: #e0e0e0 !important; } a { color: #7eb8f7 !important; } `);// 动态更新(复用同一个 style 元素) setStyle("custom-theme", `body { font-size: ${fontSize}px; }`);为什么不用 innerHTML // ❌ 安全风险:可能被 XSS 利用 style.innerHTML = css;// ✅ 推荐:textContent 不会解析 HTML style.textContent = css;textContent 只设置文本内容,不会解析 HTML 标签,比 innerHTML 更安全。 移除样式 function removeStyle(id) { const el = document.getElementById(id); if (el) el.remove(); }removeStyle("dark-mode");油猴脚本中的注意事项 油猴脚本默认在 document-end 注入,此时 document.head 已存在。如果设置了 @run-at document-start,需要等待 head 可用: // @run-at document-start function setStyle(id, css) { const target = document.head || document.documentElement; let style = document.getElementById(id); if (!style) { style = document.createElement("style"); style.id = id; target.appendChild(style); } style.textContent = css; }document.documentElement(即 <html>)在 document-start 阶段就已存在,可以作为 fallback。 GM_addStyle(油猴内置 API) Tampermonkey 提供了 GM_addStyle 作为快捷方式: // @grant GM_addStyle GM_addStyle(` .target-element { color: red; } `);不需要去重处理,但无法动态更新——每次调用都会新增一个 <style> 标签。频繁更新时还是用自定义的 setStyle。
JS 动态插入 style 标签与油猴虚拟 input 读取本地文件
动态创建 style 标签 基础写法 const style = document.createElement('style'); style.textContent = ` .my-class { color: red; font-size: 16px; } `; document.head.appendChild(style);缺点是多次调用会重复插入,页面上堆积大量 <style> 标签影响性能。 用 id 防重复插入 function addStyle(id, css) { if (document.getElementById(id)) return; const style = document.createElement('style'); style.id = id; style.textContent = css; document.head.appendChild(style); }addStyle('my-style', ` .box { background: #000; color: #fff; } `);适合油猴脚本或浏览器插件,确保只插入一次。 可覆盖更新(推荐) function setStyle(id, css) { let style = document.getElementById(id); if (!style) { style = document.createElement('style'); style.id = id; document.head.appendChild(style); } style.textContent = css; }已存在则覆盖 textContent,不堆积。 按规则粒度插入 const style = document.createElement('style'); document.head.appendChild(style);style.sheet.insertRule('.title { color: blue; }', 0);适合动态拼接规则的场景,比如根据条件添加不同的选择器。 油猴中直接用 GM_addStyle // @grant GM_addStyle GM_addStyle(` .my-class { color: green; } `);油猴环境推荐用内置 API,不需要手动操作 DOM。 注意事项style 建议放在 head 里,放 body 部分浏览器不会立即生效 频繁整块更新 textContent 会触发页面重排,CSS 内容大时注意 insertRule 插入的规则不会显示在开发者工具的 style 面板里(只在 Computed 里可见)油猴虚拟 input 读取本地文件 浏览器安全限制:文件选择框必须由用户主动触发(点击/按键),不能在页面加载时自动弹出。 封装成 Promise /** * @param {Object} options * @param {string} [options.accept] - 文件类型限制,如 ".json,.txt" * @param {string} [options.readAs] - "text" | "dataURL" | "arrayBuffer" * @param {string} [options.encoding] - 文本编码,默认 "utf-8" * @returns {Promise<{file: File, content: any}>} */ function selectFileAndRead(options = {}) { const { accept = '', readAs = 'text', encoding = 'utf-8' } = options; return new Promise((resolve, reject) => { let input = document.getElementById('__file_input__'); if (!input) { input = document.createElement('input'); input.type = 'file'; input.id = '__file_input__'; input.style.display = 'none'; document.body.appendChild(input); } input.accept = accept; input.onchange = () => { const file = input.files[0]; if (!file) { reject(new Error('未选择文件')); return; } const reader = new FileReader(); reader.onload = (e) => resolve({ file, content: e.target.result }); reader.onerror = () => reject(new Error('读取失败')); switch (readAs) { case 'dataURL': reader.readAsDataURL(file); break; case 'arrayBuffer': reader.readAsArrayBuffer(file); break; default: reader.readAsText(file, encoding); } }; input.value = ''; // 清空,否则同一文件第二次选不会触发 change input.click(); }); }使用示例 读取 JSON 文件: selectFileAndRead({ accept: '.json' }) .then(({ content }) => JSON.parse(content)) .then(data => console.log('解析结果:', data)) .catch(err => console.error(err));读取图片转 base64: selectFileAndRead({ accept: 'image/*', readAs: 'dataURL' }) .then(({ content }) => { const img = document.createElement('img'); img.src = content; document.body.appendChild(img); });Windows 文件中文乱码: selectFileAndRead({ accept: '.txt', encoding: 'gbk' }) .then(({ content }) => console.log(content));关键细节复用 input:用 id 缓存,避免多次点击在 DOM 里堆积隐藏元素 input.value = '':选同一个文件时 change 不会重触发,必须先清空 必须用户主动触发:把 selectFileAndRead() 放在按钮点击或快捷键回调里
Office 查找替换通配符规则与 VBA 正则表达式替换
Office 通配符模式(不是正则) Word / Excel 的查找替换里勾选"使用通配符"后,支持的是简化版匹配规则,和标准正则不同:通配符 含义 示例* 任意多个字符 sh_* 匹配 sh_ 开头的所有内容? 单个任意字符 sh_? 匹配 sh_ 加一个字符[0-9] 数字字符类 匹配一个数字[A-Za-z] 字母字符类 匹配一个字母[!a-z] 排除字符类 匹配不是 a-z 的字符@ 前一元素重复一次或多次(相当于 +) [0-9]@ 匹配连续数字\1 引用第一个捕获组 保留匹配部分** 在 Office 里没有特殊含义,不会"更强匹配",用一个 * 即可。 常用通配符替换示例 提取字母部分(去除数字): 查找: ([A-Za-z]*)([0-9]*) 替换: \1\1 保留第一个括号里的内容(字母部分),丢弃数字。 匹配 sh_ 开头: sh_** 匹配后面任意内容(包括空)。 只匹配 sh_ 后跟数字: sh_[0-9]@@ 在 Word 通配符里相当于正则的 +,表示重复一次或多次。 Word 删除多余空行: 查找: ^13^13 替换: ^13^13 是 Word 里的段落标记(回车符),两个连续段落标记替换成一个即可删除空行。 VBA 调用真正的正则表达式 Excel 原生不支持正则,但可以通过 VBA 调用 VBScript.RegExp 引擎: Function RegReplace(text As String, pattern As String, replacement As String) As String Dim re As Object Set re = CreateObject("VBScript.RegExp") re.Global = True re.IgnoreCase = False re.Pattern = pattern RegReplace = re.Replace(text, replacement) End Function保存后在单元格里直接用: =RegReplace(A1,"\d+","")这才是标准正则(支持 \d、\s、+、? 等)。 打开 VBA 编辑器:Excel → Alt + F11 插入 → 模块 粘贴上面的函数 保存,回到工作表使用Power Query(进阶) Excel 内置 Power Query 的 M 语言支持部分文本处理: // 提取所有数字 Text.Select([Column], {"0".."9"})// 删除数字 Text.Remove([Column], {"0".."9"})不如正则灵活,但不需要写 VBA,适合简单的批量清洗。 选择建议场景 推荐方式简单前缀/后缀匹配 Word/Excel 通配符标准正则(\d、\w、捕获组) VBA + VBScript.RegExp大量数据批量清洗 Power Query复杂逻辑处理后粘回 Excel VS Code / Notepad++ 处理后导入
Word / Excel 查找替换:通配符模式 vs 真正的正则,VBA RegExp 方案
通配符模式 ≠ 正则表达式 Office 自带的查找替换开启"使用通配符"后,支持的是简化版通配符,不是标准正则(PCRE / ECMAScript)。功能 通配符模式 标准正则任意字符 * .*单个字符 ? .数字字符类 [0-9] \d字母字符类 [A-Za-z] \w(有差异)排除字符 [!abc] [^abc]捕获组 () + \1 () + $1懒惰匹配 ❌ 不支持 *?零宽断言 ❌ 不支持 (?=...)\d+ 数字简写 ❌ 不支持 ✅Word 通配符示例 在 Word 查找替换中勾选"使用通配符": # 删除行首的数字编号(如 "1. " "12. ") 查找: [0-9]{1,}. 替换: (留空)# 把 "第X章" 中的数字替换为"第一章"形式——仅捕获数字 查找: (第)[0-9]{1,}(章) 替换: \1一\2# 删除括号内的内容(非嵌套) 查找: \([!)]*\) 替换: (留空)Word 通配符中 {n,m} 表示重复次数,不是标准正则的 {n,m}。Excel 原生通配符(非查找替换) Excel 公式里(COUNTIF、VLOOKUP 等)支持 * 和 ?,不支持字符类 [0-9]: =COUNTIF(A:A,"*错误*") ' 包含"错误"的单元格数 =COUNTIF(A:A,"????") ' 恰好4个字符查找替换对话框(Ctrl+H)同理,勾选"单元格匹配"后才能精确匹配整个单元格。 VBA 实现真正的正则(推荐) 在 Excel 里按 Alt+F11 打开 VBE,插入模块: ' 正则替换函数(可在单元格直接使用) Function RegReplace(text As String, pattern As String, replacement As String) As String With CreateObject("VBScript.RegExp") .Global = True .IgnoreCase = False .Pattern = pattern RegReplace = .Replace(text, replacement) End With End Function' 正则匹配提取函数 Function RegExtract(text As String, pattern As String) As String Dim re As Object Set re = CreateObject("VBScript.RegExp") re.Pattern = pattern re.Global = False If re.Test(text) Then RegExtract = re.Execute(text)(0).Value Else RegExtract = "" End If End Function单元格用法: =RegReplace(A1,"\d+","[数字]") ' 把数字替换为[数字] =RegReplace(A1,"^\s+|\s+$","") ' 去首尾空格(等价于 TRIM) =RegExtract(A1,"\d{11}") ' 提取11位手机号 =RegExtract(A1,"[一-龥]+") ' 提取第一段中文批量替换整列: Sub BatchRegReplace() Dim re As Object Set re = CreateObject("VBScript.RegExp") re.Global = True re.Pattern = "\s+" ' 合并多个空格 Dim cell As Range For Each cell In Range("A1:A100") If cell.Value <> "" Then cell.Value = re.Replace(cell.Value, " ") End If Next cell End SubPower Query 方案 Excel → 数据 → 获取数据 → 启动 Power Query 编辑器,M 语言示例: // 提取列中的数字 = Table.TransformColumns( Source, {{"Column1", each Text.Select(_, {"0".."9"}), type text}} )// 删除非字母数字字符 = Table.TransformColumns( Source, {{"Column1", each Text.Select(_, {"A".."Z","a".."z","0".."9"}), type text}} )Power Query 不支持完整正则,但 Text.Select / Text.Remove 加字符范围可覆盖大多数清洗需求。 Word 宏调用正则 Sub WordRegexReplace() Dim re As Object Set re = CreateObject("VBScript.RegExp") re.Global = True re.Pattern = "\b\d{4}-\d{2}-\d{2}\b" ' 匹配日期格式 YYYY-MM-DD Dim doc As Document Set doc = ActiveDocument Dim para As Paragraph For Each para In doc.Paragraphs para.Range.Text = re.Replace(para.Range.Text, "[日期]") Next para End Sub选择建议场景 方案简单通配(含*/?/字符类) 查找替换 + 通配符\d+ 等 PCRE 语法 VBA + VBScript.RegExp大批量数据清洗 Power Query需要留存为可复用公式 RegReplace 自定义函数对正则有复杂要求 导出 CSV → Python / Node.js 处理后导回
在浏览器里 hook setInterval:原理和五种写法
在浏览器里"hook" 某个内置函数,本质就是——把原函数偷出来,用你自己的函数替换它。setInterval 是最常见的 hook 目标:调试疯狂轮询、监控页面定时器、干掉反调试轮询。 一句话原理 const _origin = window.setInterval;window.setInterval = function (fn, delay, ...args) { // 你的自定义逻辑 return _origin(fn, delay, ...args); // 回到原函数 };_origin 是原函数引用,window.setInterval 是你替换后的新函数。之后所有 setInterval(...) 调用都会先经过你。 场景一:监控所有调用 (function () { const _origin = window.setInterval; window.setInterval = function (fn, delay, ...args) { console.log("setInterval:", { delay, fn: fn.toString().slice(0, 80) }); return _origin(fn, delay, ...args); }; })();用途:找出页面里到底谁在挂定时器。 场景二:按条件拦截 只关心 1 秒轮询: window.setInterval = function (fn, delay, ...args) { if (delay === 1000) { console.trace("1s interval:", fn); } return _origin(fn, delay, ...args); };console.trace 直接打出调用栈,能追到是哪段代码挂的。 场景三:阻止高频轮询 页面每 50ms 疯狂检查某个东西(典型反调试或广告轮询),CPU 直接吃满: window.setInterval = function (fn, delay, ...args) { if (delay < 500) { console.log("阻止高频 interval:", delay); return null; // 直接返回 null,不真的开定时器 } return _origin(fn, delay, ...args); };场景四:记录所有 ID 方便统一清 (function () { const _origin = window.setInterval; const ids = new Set(); window.setInterval = function (fn, delay, ...args) { const id = _origin(fn, delay, ...args); ids.add(id); return id; }; window.__clearAll = () => { ids.forEach(clearInterval); ids.clear(); console.log("全部清除"); }; })();在 devtools 里手动 __clearAll() 就能一键把当前页面所有定时器停了。 场景五:油猴脚本里 hook 必须 @run-at document-start——否则页面脚本先跑,等你 hook 上去已经晚了: // ==UserScript== // @name hook setInterval // @match https://example.com/* // @run-at document-start // @grant none // ==/UserScript==(function () { const _origin = window.setInterval; window.setInterval = function (fn, delay, ...args) { console.log("intercepted:", delay); return _origin.call(window, fn, delay, ...args); }; })();注意用 _origin.call(window, ...)——某些浏览器的 setInterval 严格要求 this === window,不 bind 会抛 Illegal invocation。 常见反 hook 手法 页面可能这样绕过你: // 提前保存原始引用 const _real = window.setInterval.bind(window); _real(fn, 100);页面代码保存的是你的 hook 前的原函数,你就拦不到 或者 iframe 里跑,window 隔离 或者用 postMessage + Worker,压根不走主线程 setInterval对策:@run-at document-start 越早越好 同时 hook setTimeout / requestAnimationFrame——很多站点会混着用 iframe 里的定时器要另外注入用 Proxy 更隐蔽 上面的写法暴露痕迹(window.setInterval.toString() 会变),要更隐蔽: window.setInterval = new Proxy(window.setInterval, { apply(target, thisArg, args) { const [fn, delay] = args; console.log("intercepted:", delay); return Reflect.apply(target, thisArg, args); }, });Proxy 版本的 .toString() 还是原始的,页面用 toString().includes("[native code]") 检测更难发现。 一句话总结 hook = 存原函数引用 + 替换 window 属性。油猴里必须 run-at document-start,进阶用 Proxy 更隐蔽。同一套模板套 setTimeout / fetch / XMLHttpRequest 也照样能用。
