jQuery 依赖移除与原生 JS 迁移指南
自 2026-07-24 起,XIUNOX 已系统性移除所有 jQuery 依赖。本文档记录迁移原则、关键修复页面改造规范、以及 jQuery → 原生 JS API 对照表,供插件开发者参考。
一、背景
一、安全漏洞风险(XIUNOX 重构首要解决痛点)
大范围 XSS 跨站脚本漏洞
旧版 jQuery(3.5.0 以下,Xiuno 原版长期捆绑老旧 jQuery)的
.html()、.append()、$()动态 HTML 解析存在缺陷,攻击者可构造带onerror/onclick恶意事件标签注入脚本,窃取用户 Cookie、劫持会话,对应 CVE-2020-11022/11023 高危漏洞CSDN博...。论坛大量弹窗、AJAX 加载帖子、评论渲染均依赖该 API,原生前端过滤极易被绕过。
原型污染漏洞
3.4.0 前版本
$.extend对象合并逻辑缺陷,传入含__proto__恶意参数可污染全局 JS 原型,篡改站点全局逻辑,存在后台越权风险LTD枢纽...。AJAX 接口缺少标准化安全防护
原版
$.ajax调用无统一 CSRF 校验封装,大量插件、前台提交接口遗漏令牌校验,容易触发 CSRF 跨站请求伪造;XIUNOX 重构强制用htmx/fetch替代$.ajax统一携带 CSRF 令牌,消除该隐患稀土掘金。第三方依赖攻击面扩大
jQuery 是全站强制引入的第三方 JS 库,只要存在未修复版本,全站所有页面共享漏洞;各类第三方插件还会重复打包不同版本 jQuery,版本混乱更难统一修复。
二、性能与加载损耗问题
冗余体积拖慢页面加载
jQuery 压缩后仍有约 30KB,每个页面强制加载,增加首屏请求体积、延长关键渲染路径;移动端弱网场景下延迟明显,现代浏览器原生
querySelector/fetch/classList可完全替代,无需额外库开销highfivest...。DOM 操作双层抽象损耗
jQuery 选择器、DOM 方法会先内部解析语法、封装一层逻辑再调用原生 API,频繁渲染帖子列表、动态加载分页时,比原生 JS 执行效率更低;论坛海量帖子、评论动态渲染场景性能短板突出。
事件绑定内存泄漏隐患
动态刷新内容时,若未手动
.off()解绑 jQuery 事件,会重复堆积事件监听,长期浏览页面造成内存持续上涨,出现弹窗关闭后滚动异常、按钮点击失效等 BUG(Xiuno 原版已知 UI 缺陷)Programmin...。
三、技术栈兼容与升级阻碍
Bootstrap 5 彻底抛弃 jQuery 依赖
XIUNOX 升级 Bootstrap5.3 后,原有基于 jQuery 的弹窗、下拉、分页组件全部失效,两套 DOM 操作 API 并存(jQuery $ + 原生 JS),模板、插件代码大量冲突,必须逐行清理 jQuery 调用代码稀土掘金。
插件生态分裂,兼容成本极高
论坛存量插件分两类:老插件重度依赖
$、$.ajax;新现代化插件使用 HTMX / 原生 JS。官方开发插件兼容性扫描器专门检测$.ajax(、jQuery 组件调用,批量标记不兼容代码,重构时需要逐个改造所有插件,维护成本巨大稀土掘金。新旧浏览器兼容逻辑累赘
旧 jQuery 内置大量 IE 兼容代码,而 XIUNOX 目标环境为现代浏览器 + PHP8,冗余兼容代码无使用价值,徒增代码体积;新版 jQuery4.0 大幅删减旧浏览器兼容,但升级又会引发大量 API 破坏性变更。
前后端现代化架构不匹配
XIUNOX 采用 HTMX 无刷新、RESTful API 现代化架构,HTMX 原生基于
fetch,和$.ajax回调式写法割裂,同时 jQuery 回调地狱不利于异步逻辑维护,无法统一异步请求规范。
四、项目长期维护与代码架构缺陷
命令式 DOM 代码难以维护
jQuery 是命令式操作,代码散落在各处直接操作 DOM,无组件化、数据驱动逻辑;论坛弹窗、分页、评论加载代码碎片化,形成 “面条代码”,新增功能、修复 BUG 时极易产生联动问题稀土掘金。
版本升级破坏性变更多
jQuery 大版本(1.x→3.x→4.x)存在大量废弃 API、行为修改:自动补全 px 单位、选择器语法、Deferred 异步逻辑全部改动;Xiuno 原版基于老旧 1.x/2.x,升级需要全站模板、JS 脚本逐行适配,工作量极大。
** 全局
\(变量污染** jQuery挂载全
\)变量,插件、自定义脚本容易出现命名冲突,引发随机脚本失效;模块化 ES6 环境下和原生变量、其他库冲突概率更高。
历史路径
1.1.3及之前版本兼容:项目内置 jQuery 兼容 (
view/js/xiuno-modern.js暴露window.jQuery = $),让 20 个存量插件 JS 和.htm模板内联$代码继续工作。新插件代码强制用XN.*API。1.1.4版本移除:系统性移除所有 jQuery 依赖。68 个文件中的
$/XN.confirm调用全部改造为原生 JS。关键修复页面(在线升级/数据库升级/后台登录等)已完全不依赖xiuno-modern.js,使用原生fetch+confirm+ Web API 实现,避免「网站坏 → 修复页面也坏 → 无法修复」的死循环。
设计原则
零外部依赖:关键修复页面(在线升级、数据库升级、后台登录、系统工具)禁止依赖
xiuno-modern.js的$/XNshim,使用原生fetch+confirm+querySelectorAll等 Web API原生 JS 优先:新代码强制使用原生 JS + htmx 4 属性,禁止使用
$/jQuery/$.fn.*XN API 保留:
XN.toast()/XN.ajax()/XN.confirm()/XN.alert()等高层 API 仍在xiuno-modern.js中提供,非关键页面可继续使用(前后台footer.inc.htm已全局加载xiuno-modern.js)安全兜底:调用
XN.confirm/XN.confirmCreditsDeduct等异步 API 时必须加typeof守卫 +try-catch,避免 shim 加载失败导致按钮永久禁用
二、关键修复页面规范
定义:用户修复网站问题的「最后手段」页面。旧版本站点可能没有
xiuno-modern.js或该文件加载失败时,这些页面必须仍然能工作。
关键修复页面清单
文件 | 用途 |
|---|---|
| 在线升级(检查更新、下载、应用) |
| 数据库结构升级 |
| 后台登录 |
| 插件管理(启用/禁用/卸载) |
| 系统工具(清缓存) |
关键页面改造检查表
内联 JS 使用原生
fetch+querySelectorAll+confirm,不依赖$/XNAJAX 请求用自实现的
ajax()函数(基于fetch),不调用$.ajax/XN.ajaxCSRF token 通过 PHP
json_encode注入 JS 变量,请求中显式传递(header + body 双保险)动态内容插入 DOM 前用
escapeHtml()转义,防止 XSSToast 提示独立实现,不依赖
XN.toast(用#toast-container或alert兜底)按钮状态管理用原生
disabled+innerHTML,不依赖$.fn.button('loading')事件监听用
addEventListener,不依赖$.fn.on
范式:原生 ajax 函数
JavaScript
// ponytail: 关键修复页面必须独立于 xiuno-modern.js
function buildQuery(data) {
var parts = [];
for (var k in data) {
if (Object.prototype.hasOwnProperty.call(data, k)) {
parts.push(encodeURIComponent(k) + '=' + encodeURIComponent(data[k]));
}
}
return parts.join('&');
}
function ajax(opts) {
var url = opts.url;
var type = (opts.type || 'GET').toUpperCase();
var data = opts.data || null;
var dataType = opts.dataType || 'json';
var fetchOpts = {
method: type,
headers: {
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': csrfToken
},
credentials: 'same-origin'
};
if (type === 'POST' && data) {
if (data instanceof FormData) {
fetchOpts.body = data;
} else {
fetchOpts.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
fetchOpts.body = typeof data === 'object' ? buildQuery(data) : data;
}
} else if (data && !(data instanceof FormData)) {
var sep = url.indexOf('?') === -1 ? '?' : '&';
url += sep + buildQuery(data);
}
fetch(url, fetchOpts).then(function(response) {
return response.text().then(function(text) {
var xhrLike = { status: response.status, responseText: text };
if (!response.ok) throw xhrLike;
if (dataType === 'json') {
try { return JSON.parse(text); }
catch (e) { throw xhrLike; }
}
return text;
});
}).then(function(res) {
if (opts.success) opts.success(res);
}).catch(function(err) {
var xhr = err && err.responseText !== undefined ? err : { responseText: (err && err.message) ? err.message : String(err || '') };
if (opts.error) opts.error(xhr);
});
}范式:独立 Toast 函数
JavaScript
// ponytail: 不依赖 xiuno-modern.js 的 showToast/XN.toast
// 复用 footer.inc.htm 已渲染的 #toast-container(若无则 fallback 到 alert)
function showToast(message, type) {
var container = document.getElementById('toast-container');
if (!container) { alert(message); return; }
var toastEl = document.createElement('div');
toastEl.className = 'toast show align-items-center text-white bg-' + (type === 'success' ? 'success' : (type === 'error' ? 'danger' : 'secondary'));
toastEl.setAttribute('role', 'alert');
var body = document.createElement('div');
body.className = 'd-flex';
var inner = document.createElement('div');
inner.className = 'toast-body';
inner.textContent = message;
body.appendChild(inner);
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'btn-close btn-close-white me-2 m-auto';
btn.setAttribute('data-bs-dismiss', 'toast');
btn.onclick = function() { toastEl.remove(); };
body.appendChild(btn);
toastEl.appendChild(body);
container.appendChild(toastEl);
setTimeout(function() { if (toastEl.parentNode) toastEl.remove(); }, 4000);
}三、jQuery → 原生 JS API 对照表
选择器
jQuery | 原生 JS | 备注 |
|---|---|---|
|
| 返回单个元素 |
|
| 返回 NodeList |
|
| 单个 |
|
| NodeList 支持 forEach |
|
| 直接绑定 |
|
| 事件委托 |
AJAX
jQuery | 原生 JS | 备注 |
|---|---|---|
| 自实现 | 基于 |
|
| 简单场景 |
|
| 简单场景 |
| 自实现 | PHP 风格数组 |
|
| 表单序列化 |
DOM 操作
jQuery | 原生 JS | 备注 |
|---|---|---|
|
| 注意 XSS,需先 |
|
| 安全(自动转义) |
|
| 读取 |
|
| 设置 |
|
| |
|
| |
|
| HTML5 data-* |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| 原生支持 |
事件
jQuery | 原生 JS | 备注 |
|---|---|---|
|
| |
|
| |
|
| |
|
| 一致 |
|
| 一致 |
|
| 原生 |
工具函数
jQuery | 原生 JS | 备注 |
|---|---|---|
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
|
Bootstrap 组件桥接
jQuery | 原生 JS | 备注 |
|---|---|---|
|
| |
|
| |
|
| |
|
| |
|
|
四、异步确认弹窗的兜底保护
XN.confirm / XN.confirmCreditsDeduct 等异步 API 在 xiuno-modern.js 加载失败时会抛 TypeError。若在 htmx:confirm 事件中调用且未加保护,会导致 window._htmxConfirmAsync 标志位卡在 true,提交按钮永久禁用。
范式:带兜底的异步确认
JavaScript
form.addEventListener('htmx:confirm', function(e) {
e.preventDefault();
// ponytail: 兜底保护——若 XN 不可用,直接放行请求
if (typeof XN === 'undefined' || typeof XN.confirmCreditsDeduct !== 'function') {
e.detail.issueRequest();
return;
}
window._htmxConfirmAsync = true;
try {
XN.confirmCreditsDeduct(creditsEvent, fid, function() {
window._htmxConfirmAsync = false;
e.detail.issueRequest();
}, { onCancel: function() {
window._htmxConfirmAsync = false;
e.detail.dropRequest();
if (window.resetPostSubmit) window.resetPostSubmit();
} });
} catch(err) {
// ponytail: 抛异常时必须重置标志并恢复按钮
window._htmxConfirmAsync = false;
if (window.resetPostSubmit) window.resetPostSubmit();
if (typeof console !== 'undefined' && console.error) console.error('confirm failed:', err);
}
});五、XSS 防护:escapeHtml 函数
动态内容插入 innerHTML 前必须转义。关键修复页面不依赖 XN.escapeHtml,自实现:
JavaScript
function escapeHtml(s) {
if (s === null || s === undefined) return '';
return String(s).replace(/[&<>"']/g, function(c) {
return {'&':'&','<':'<','>':'>','"':'"',"'":'''}[c];
});
}
// 使用示例
resultEl.innerHTML = escapeHtml(res.message);
detailEl.innerHTML = '<div>' + icon + ' ' + escapeHtml(r.name) + '</div>';非关键页面可继续使用
XN.escapeHtml(untrustedString)(xiuno-modern.js提供)。
六、迁移检查清单
插件 JS 文件迁移
逐项核对:
Grep 旧文件所有
$调用:$.ajax/$.xpost/$.get/$.post/$.each/$.param/$.fn.*/$('...')每个调用替换为原生 JS 等价实现(参见对照表)
XN.confirm/XN.alert改为原生confirm/alert(或保留XN.confirm但加typeof守卫)表单序列化用
new FormData(form)或new URLSearchParams事件委托改为
document.addEventListener('click', e => { if (e.target.closest(sel)) ... })确认无
<script src>引用旧 jQuery 文件修改后清
tmp/缓存,提示用户硬刷新
关键修复页面额外检查
无
$.ajax/$()/$.each/XN.confirm等依赖CSRF token 通过 PHP
json_encode注入 JS 变量AJAX 请求显式传递 CSRF token(header + body 双保险)
动态内容插入 DOM 前用
escapeHtml()转义Toast 提示独立实现,有
alert兜底按钮状态管理用原生
disabled+innerHTML
七、常见迁移陷阱
1. return false 在原生事件中不阻止默认行为
JavaScript
// ❌ jQuery 写法:return false 等价于 preventDefault + stopPropagation
$(el).on('submit', function() { ... return false; });
// ✅ 原生写法:必须显式调用
el.addEventListener('submit', function(e) {
e.preventDefault();
e.stopPropagation();
// ...
});项目的
$shim 已在$.fn.on内部包装 handler,检测ret === false时调e.preventDefault() + e.stopPropagation()。但新代码仍建议显式接收e参数并调用e.preventDefault()(双保险)。
2. $.param 数组序列化格式
JavaScript
// ❌ jQuery $.param({ids: [1,2,3]}) → "ids[]=1&ids[]=2&ids[]=3"
// 原生 URLSearchParams 会把数组隐式 toString 为 "1,2,3"
// ✅ 自实现 buildQuery 支持 PHP 风格数组
function buildQuery(data) {
var parts = [];
function build(prefix, val) {
if (Array.isArray(val)) {
for (var i = 0; i < val.length; i++) build(prefix + '[]', val[i]);
} else if (typeof val === 'object') {
for (var k in val) {
if (Object.prototype.hasOwnProperty.call(val, k)) {
build(prefix ? prefix + '[' + k + ']' : k, val[k]);
}
}
} else {
parts.push(encodeURIComponent(prefix) + '=' + encodeURIComponent(val === null || val === undefined ? '' : val));
}
}
build('', data);
return parts.join('&').replace(/%20/g, '+');
}3. FormData 不能预设 Content-Type
JavaScript
// ❌ 错误:手动设 Content-Type 会丢失 boundary
fetchOpts.headers['Content-Type'] = 'multipart/form-data';
fetchOpts.body = formData;
// ✅ 正确:不设 Content-Type,浏览器自动加 boundary
if (data instanceof FormData) {
fetchOpts.body = data;
// 不要设 Content-Type
}4. NodeList.forEach 兼容性
JavaScript
// ✅ 现代浏览器支持
document.querySelectorAll('.btn').forEach(function(el) { ... });
// ✅ 兼容旧浏览器
var els = document.querySelectorAll('.btn');
Array.prototype.forEach.call(els, function(el) { ... });5. 异步确认弹窗的按钮状态管理
htmx:confirm 事件中调用异步 API(如 XN.confirm)时,必须设置 window._htmxConfirmAsync = true 阻止 setTimeout(0) 提前恢复按钮。回调/取消/异常时必须重置为 false。详见第四节范式。
对 Xiuno 这类传统论坛系统而言,jQuery 核心矛盾是:遗留安全漏洞无法彻底根除、拖累页面性能、阻碍 Bootstrap5/PHP8/HTMX 现代化技术栈升级、插件生态维护成本极高、代码架构落后难以长期迭代,因此 XIUNOX 重构方案选择完全移除 jQuery,改用原生现代前端方案。
