在日常冲浪或开发调试时,很多人都用过一种极客小技巧——小书签(Bookmarklet)。只要在浏览器的收藏夹栏新建一个书签,把名称改成自定义功能名(比如 f1.75x),并在 URL 一栏填入一段以 javascript: 开头的代码,就能打造一个随点随用的网页外挂。
比如,下面这串经典的“全网音视频强制 1.75 倍速”书签脚本:
javascript:(function(){function forceSpeed(s=1.75){document.querySelectorAll("video,audio").forEach(m=>{if(m.playbackRate!==s)m.playbackRate=s;if(!m._lock){m._lock=!0;m.addEventListener("ratechange",()=>{if(m.playbackRate!==s)m.playbackRate=s;});}});}setInterval(()=>forceSpeed(1.75),500);})();
它的原理非常巧妙:遍历页面上所有的 <video> 和 <audio> 标签,强行将播放倍速锁定为 1.75,同时监听 ratechange 事件并使用 setInterval 每 500 毫秒巡检一次,防止网页自带的播放器把倍速改回去。
既然书签脚本这么强大,一个很自然的想法就诞生了:
平时遇到禁止复制文本、禁用右键菜单、或者弹窗广告满天飞的流氓网页时,我们经常需要打开 F12 开发者工具,进入“设置 -> 首选项 -> 调试程序”,手动勾选“禁用 JavaScript”。能不能也写一段类似的书签脚本,放到收藏夹栏里,点一下禁用 JS,再点一下恢复呢?
本文将从浏览器底层安全机制出发,剖析为什么“直接禁用”是一个不可能完成的悖论,并手把手带你写出一段支持一键冻结、再点恢复的“网页级 JS 开关”书签脚本,最后汇总全网最高效的 JS 开关方案。
一、 为什么书签脚本无法直接勾选“禁用 JavaScript”?
如果我们的目标是让书签脚本实现与开发者工具中勾选“禁用 JavaScript”完全相同的底层效果,在技术上会撞上两堵无法逾越的高墙:
1. “我杀我自己”的逻辑死锁
小书签的本质,是利用浏览器的 javascript: 伪协议,在当前页面的上下文中动态执行一段 JS 代码。
假设你点击第一下书签,成功在浏览器引擎层面彻底关闭了当前页面的 JavaScript 执行权限;那么当你想要点击第二下恢复时,由于浏览器已经拒绝执行任何 javascript: 协议的代码,这枚书签就变成了一颗“哑弹”,永远无法再次触发自己来恢复环境。
2. 浏览器沙盒与权限隔离(Sandbox)
现代浏览器有着严格的权限分级。网页内部运行的脚本(包括 Bookmarklet)处于最底层的页面沙盒(Page Context)中,它只能操作当前页面的 DOM 树、window 对象和 BOM 接口。而“是否允许运行 JavaScript”属于浏览器内核配置或 DevTools 调试器(Debugger)级别的特权开关,网页脚本没有任何 API 权限去越界修改浏览器的首选项设置。
二、 破局思路:打造“网页级 JS 假死/恢复”书签脚本
既然无法从浏览器引擎层面“拔电源”,我们不妨换个角度思考:我们在网页上禁用 JS,真正的痛点是什么?
90% 的情况下,我们禁用 JS 是为了干掉这三件事:
- 破解限制:网页通过监听
copy、contextmenu、selectstart、keydown等事件,阻止你复制文字、弹出右键菜单或按 F12。 - 阻止干扰:网页通过
click、mousedown等事件在你点击页面时乱弹广告窗口,或者拦截你的正常交互。 - 停止轮询与动画:网页通过
setInterval、setTimeout或requestAnimationFrame不断刷新弹窗、检测状态或执行烦人的动态效果。
只要我们在不关闭浏览器 JS 引擎的前提下,用一段脚本把页面上所有的定时器全部清空并拦截,同时在事件传播的最顶层截断网页自身的所有交互监听,就能让网页的 JS 瞬间进入“脑死亡(冻结)”状态!而且因为浏览器 JS 引擎本身还活着,你随时可以再点一下书签把拦截器关掉,完美实现“一键冻结、再点还原”。
1. 终极书签代码(单行纯净版,可直接复制)
像平时添加脚本书签一样,在收藏夹栏新建一个书签:
- 名称:
开关JS(或任意你喜欢的名字) - URL:完整复制粘贴下方这行代码
javascript:(function(){const evts=['copy','cut','paste','contextmenu','selectstart','dragstart','mousedown','mouseup','click','dblclick','keydown','keyup','keypress'];if(!window._jsBlocker){window._jsBlocker={active:false,fn:function(e){if(window._jsBlocker.active)e.stopImmediatePropagation();},st:window.setTimeout,si:window.setInterval,raf:window.requestAnimationFrame};evts.forEach(t=>window.addEventListener(t,window._jsBlocker.fn,true));window.setTimeout=function(...a){return window._jsBlocker.active?0:window._jsBlocker.st.apply(this,a);};window.setInterval=function(...a){return window._jsBlocker.active?0:window._jsBlocker.si.apply(this,a);};window.requestAnimationFrame=function(...a){return window._jsBlocker.active?0:window._jsBlocker.raf.apply(this,a);};}window._jsBlocker.active=!window._jsBlocker.active;if(window._jsBlocker.active){let id=window._jsBlocker.st.call(window,()=>{},0);while(id--){window.clearTimeout(id);window.clearInterval(id);}}const tip=document.createElement('div');tip.textContent=window._jsBlocker.active?'🚫 页面JS已冻结(再点恢复)':'✅ 页面JS已恢复';tip.style.cssText='position:fixed;top:20px;right:20px;z-index:2147483647;background:'+(window._jsBlocker.active?'#d93025':'#1e8e3e')+';color:#fff;padding:10px 16px;border-radius:8px;font:14px sans-serif;box-shadow:0 4px 12px rgba(0,0,0,.3);transition:opacity .3s;pointer-events:none;';document.body.appendChild(tip);window._jsBlocker.st.call(window,()=>{tip.style.opacity='0';window._jsBlocker.st.call(window,()=>tip.remove(),300);},1500);})();
2. 核心源码拆解(它是如何工作的?)
为了方便阅读和二次修改,我们将上面的压缩代码还原成带有详细注释的格式:
(function() {
// 1. 定义需要拦截的常见交互与限制事件
const evts = [
'copy', 'cut', 'paste', 'contextmenu', 'selectstart', 'dragstart',
'mousedown', 'mouseup', 'click', 'dblclick', 'keydown', 'keyup', 'keypress'
];
// 2. 初始化全局控制器(仅在第一次点击时挂载)
if (!window._jsBlocker) {
window._jsBlocker = {
active: false, // 当前开关状态
// 事件拦截函数:一旦开启,立即阻止后续所有事件监听器执行
fn: function(e) {
if (window._jsBlocker.active) {
e.stopImmediatePropagation();
}
},
// 备份原生的计时器函数,供恢复和自身弹窗提示使用
st: window.setTimeout,
si: window.setInterval,
raf: window.requestAnimationFrame
};
// 利用事件捕获阶段(第三个参数为 true)在最顶层挂载拦截器
evts.forEach(t => window.addEventListener(t, window._jsBlocker.fn, true));
// 劫持并重写全局定时器:当处于冻结状态时,直接罢工返回 0
window.setTimeout = function(...a) {
return window._jsBlocker.active ? 0 : window._jsBlocker.st.apply(this, a);
};
window.setInterval = function(...a) {
return window._jsBlocker.active ? 0 : window._jsBlocker.si.apply(this, a);
};
window.requestAnimationFrame = function(...a) {
return window._jsBlocker.active ? 0 : window._jsBlocker.raf.apply(this, a);
};
}
// 3. 切换开关状态(开 -> 关,关 -> 开)
window._jsBlocker.active = !window._jsBlocker.active;
// 4. 如果切换为“冻结”状态,暴力清空页面上已经存在的所有定时器
if (window._jsBlocker.active) {
let id = window._jsBlocker.st.call(window, () => {}, 0);
while (id--) {
window.clearTimeout(id);
window.clearInterval(id);
}
}
// 5. 在页面右上角生成可视化的状态提示气泡(Toast)
const tip = document.createElement('div');
tip.textContent = window._jsBlocker.active ? '🚫 页面JS已冻结(再点恢复)' : '✅ 页面JS已恢复';
tip.style.cssText =
'position:fixed;top:20px;right:20px;z-index:2147483647;' +
'background:' + (window._jsBlocker.active ? '#d93025' : '#1e8e3e') + ';' +
'color:#fff;padding:10px 16px;border-radius:8px;font:14px sans-serif;' +
'box-shadow:0 4px 12px rgba(0,0,0,.3);transition:opacity .3s;pointer-events:none;';
document.body.appendChild(tip);
// 使用备份的原生 setTimeout 让提示框在 1.5 秒后自动淡出销毁
window._jsBlocker.st.call(window, () => {
tip.style.opacity = '0';
window._jsBlocker.st.call(window, () => tip.remove(), 300);
}, 1500);
})();
这段代码有三个精妙的设计细节:
- 捕获阶段精准截胡(
useCapture: true+stopImmediatePropagation):
DOM 事件流分为“捕获阶段 -> 目标阶段 -> 冒泡阶段”。绝大多数网页脚本绑定事件都挂载在document、body或具体元素上(默认冒泡阶段)。我们的脚本直接挂载在最顶层的window捕获阶段,一旦触发立刻调用e.stopImmediatePropagation(),不仅阻止事件往下传,连同级后续监听器也一并掐断。更妙的是,因为我们没有调用e.preventDefault(),所以浏览器原生的鼠标选中文字、Ctrl+C复制、右键菜单、页面滚动依然畅通无阻! - 递增 ID 逆向清场:
浏览器中setTimeout和setInterval共享一套自增的数字 ID 池。当我们创建一个空的setTimeout获取当前最大id后,通过while(id--)循环逆向清除,就能把页面此前埋下的所有定时炸弹一次性排空。 - 后门保留(保存原生引用):
虽然我们把window.setTimeout废掉了,但我们把原生的计时器保存在了window._jsBlocker.st里。这样一方面再次点击书签时可以无损恢复,另一方面我们的右上角状态提示框(Toast)依然能调用原生计时器实现 1.5 秒自动消失。
三、 如果真的需要“底层彻底禁用 JS”,最快的方法是什么?
虽然上面的书签脚本能解决 90% 的日常需求,但如果你在进行前端调试(比如测试网页的 <noscript> 降级表现,或者阻止首屏渲染时的网络请求脚本),确实需要原汁原味的浏览器级“禁用 JavaScript”功能,以下两种方法可以让你告别繁琐的设置菜单翻找:
方法 1:使用浏览器扩展(真正的一键切换)
浏览器扩展(Extension)运行在特权环境中,拥有调用 chrome.contentSettings.javascript 的底层权限。即使当前网页的 JS 被徹底禁用,浏览器右上角的扩展图标依然可以正常点击。
- 推荐工具:在 Chrome 或 Edge 扩展商店搜索安装 Quick Javascript Switcher。
- 体验:将图标固定在地址栏右侧,点一下图标变红(当前域名彻底禁用 JS 并自动刷新),再点一下变绿(立刻恢复)。
方法 2:DevTools 命令面板快捷键(免装任何插件)
如果你已经打开了 F12 开发者工具,千万不要再去点右上角齿轮、进“首选项”、滑到最下面找“调试程序 -> 禁用 JavaScript”了,只需三步肌肉记忆:
- 在 F12 界面按下快捷键
Ctrl + Shift + P(Mac 为Cmd + Shift + P)呼出 Run Command 命令面板。 - 直接敲键盘输入
js。 - 第一项高亮显示的就是“禁用 JavaScript(Disable JavaScript)”,直接按
回车(Enter)即可生效;想恢复时,再按一次Ctrl + Shift + P->js->回车即可立刻开启。
四、 三种方案横向对比
为了方便你在不同场景下挑选最趁手的工具,可以把这三种方案总结为下表:
| 对比维度 | 方案一:本文“冻结JS”书签脚本
| 方案二:Quick JS Switcher 扩展 | 方案三:DevTools 快捷键 (Ctrl+Shift+P) | ||
|---|---|---|---|
| 操作方式 | 点击书签栏图标一键切换 | ||
| 点击浏览器右上角扩展图标 | 按 F12 后输入 js 回车 | ||
| 是否需要安装插件 | ❌ 免安装,纯代码书签 | ||
| ✅ 需安装浏览器扩展 | ❌ 浏览器原生自带 | ||
| 是否破坏已加载页面 | 🟢 不刷新页面,保留当前阅读进度与已渲染内容 | 🟡 通常需刷新页面以彻底生效 | 🟢 不刷新页面,立即阻断后续脚本执行 |
| 作用深度 | 网页 DOM 事件与计时器层(软冻结) | 浏览器站点权限层(硬禁用) | 浏览器调试器引擎层(硬禁用) |
| 最佳适用场景 | 破解禁止复制/右键、停掉烦人弹窗与轮询脚本 | 频繁针对特定网站永久或临时开关 JS | 前端开发调试、测试无 JS 环境表现 |
把文章开头的 1.75倍速 脚本和今天这枚 开关JS 脚本一起放进你的浏览器收藏夹栏,一个对付流媒体视频,一个对付流氓网页脚本,你的日常冲浪体验绝对会清爽一大截!
版权属于:soarli
本文链接:https://blog.soarli.top/archives/1221.html
转载时须注明出处及本声明。