利用 Object.defineProperty 解决 window.onload 覆盖问题

作者:代码拾荒

时间:2026-10-23

浏览:182

在老项目维护中,多个脚本加载可能导致 window.onload 回调被覆盖。本文介绍一种利用 Object.defineProperty 劫持赋值行为的方案,将回调函数存入队列集中处理,无需修改原有打包代码即可防止后续加载覆盖先前的 onload 逻辑。

文章目录

问题背景与常见方案局限

在维护老旧项目时,由于页面结构重组,不同页面的 JavaScript 代码可能被加载到同一页面中。这导致一个页面内存在多个 window.onload 定义,其直接后果是前面的回调函数会被后续的赋值操作覆盖掉。

  1. 常见的解决方案主要有两种:
  2. 一是重载 window.onload 方法;二是推荐使用 window.addEventListener。

然而,上述方法要么仅能预防新覆盖,要么需要修改原有代码。由于本项目中的老代码已通过 webpack 打包且配置文件遗失,维护人员曾直接修改打包后的代码,重新打包的工作量巨大,因此只能采取渐进式修改策略。

基于 Object.defineProperty 的解决方案

经过权衡,决定采用 Object.defineProperty 来劫持 window.onload 的赋值行为,将对应的回调函数收集到一个队列中,最后统一执行处理。

具体实现代码如下:

核心实现代码


/**
 * @function windowLoadInit - 劫持 window.onload 的赋值行为,防止覆盖
 * @desc 函数调用前产生的覆盖不可逆转
 * @throw {Any} 所有回调执行完毕之后,会抛出 catch 到的第一个错误
 *  错误将被异步抛出,避免影响初始化函数的继续执行
 * @return {Function} 
 */
function windowLoadInit(){
 const eventQueue = [];
 
 // 防止覆盖之前的 window.onload
 window.onload instanceof Function && eventQueue.push(window.onload); 
 
 window.onload = e => {
  const errQueue = [];
  // 逐个处理回调事件
  while(!!eventQueue.length){
    try{
      eventQueue.shift()(e);
    } catch(err){
      errQueue.push(err);
    }
  }
  
  if(!!errQueue.length) {
    setTimeout(() => {
      throw errQueue.shift();
    },0);
  };
 };
 
 // 每次赋值时,将回调函数添加到队列
 Object.defineProperty(window, 'onload', { 
  set: eventQueue.push
 }); 
 
 return window.onload
}

测试代码如下:

测试用例验证


// 此 testCase 需在页面加载完成前执行
function testCase(){
 const arr = [];
 window.onload = () => arr.push(-1); // 这个回调的覆盖不可避免
 window.onload = () => arr.push(0);
 windowLoadInit();
 window.onload = () => arr.push(1);
 window.onload = () => arr.push(2);
 
 return new Promise(resolve => {
  window.onload = () => resolve(arr.join('') === '012')
 })
}
testCase().then(console.log); // true

以上即为本文的全部内容,希望能为您的学习提供有益参考,欢迎继续支持本站。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章