不修改原JS文件时如何替换覆盖已声明函数的实现
所有方案生效的核心前提:你编写的独立JS文件,必须在原始JS文件之后加载,否则覆盖逻辑会先于原函数声明执行,导致失效。
方案1:直接重写全局函数(最简单,适配示例场景)
如果原函数是声明在全局作用域的普通函数(给出的示例就属于这种情况),全局函数本质是window对象的属性,直接重新赋值同名属性即可完成覆盖:
window.SayHello = function() { $(".nav").addClass('fadeUp'); }
适用场景:原函数挂载在全局作用域,没有被闭包、模块作用域私有化。
局限性:如果原函数是闭包内部/ES模块/CommonJS模块内部的私有变量,全局无法访问到原函数引用,这个方案不生效。
如果你的JS可以在原JS之前加载(比如油猴脚本、自定义CDN注入场景),可以用属性拦截的方式强制覆盖,避免原函数后续声明冲掉你的逻辑:
// 需在原JS加载前执行 const customSayHello = function() { $(".nav").addClass('fadeUp'); } Object.defineProperty(window, 'SayHello', { get() { return customSayHello; }, set() { // 忽略原文件对SayHello的赋值,始终返回自定义逻辑 return customSayHello; }, configurable: true })
方案2:猴子补丁(Monkey Patch)劫持
如果你需要保留原函数的调用能力(比如在原逻辑前后追加逻辑、修改入参/返回值),可以先缓存原函数的引用,再挂载新的实现:
// 缓存原函数引用 const _originalSayHello = window.SayHello; window.SayHello = function(...args) { // 这里写新逻辑,完全不调用_originalSayHello就是彻底替换原逻辑 $(".nav").addClass('fadeUp'); // 如果需要叠加原逻辑,放开下面注释即可,还可以自定义传入参数、修改返回结果 // return _originalSayHello.apply(this, args); }
适用场景:需要兼容原逻辑、不想彻底废弃原函数能力的场景。
方案3:事件回调场景的解绑重绑
如果原函数是作为DOM事件的回调被提前绑定(比如示例中的导航栏sticky效果,大概率是绑定在scroll事件上触发),单纯重写window上的函数不会生效——因为事件绑定时已经持有了原函数的独立引用,不会读取后续重写的window属性。这种情况需要先解绑原事件,再绑定新逻辑:
// 以jQuery的scroll事件为例,先解绑原回调 $(window).off('scroll', window.SayHello); // 绑定新的处理逻辑 $(window).on('scroll', function() { $(".nav").addClass('fadeUp'); });
方案4:构建层面替换(适配模块化工程场景)
如果项目是用webpack/Vite/Rollup等构建工具搭建的模块化工程,原函数属于模块内部导出的方法,无法通过全局访问覆盖,可以通过构建配置的alias(路径别名)能力,把原文件的引入路径映射到你自己写的新JS文件上,在新文件中实现需要的逻辑、保持导出接口和原文件一致即可,不需要修改原有业务代码的引入逻辑。
内容的提问来源于stack exchange,提问作者Giannis

