动态脚本加载的几种模式

在现代Web开发中,动态加载JavaScript脚本是一项常见且重要的技术。通过动态脚本加载,我们可以实现按需加载、提高页面性能、实现模块化等目标。本文将介绍几种常见的动态脚本加载模式及其实现方式。

1. 基本的动态脚本加载

最基本的动态脚本加载方式是使用DOM API创建<script>元素并添加到文档中:

javascript 复制代码
function loadScript(url) {
  const script = document.createElement('script');
  script.src = url;
  document.head.appendChild(script);
}

// 使用示例
loadScript('https://example.com/script.js');

这种简单的方式适用于不需要处理加载状态的基本场景。

2. 带回调的动态加载

为了知道脚本何时加载完成,我们可以添加onloadonerror事件处理程序:

javascript 复制代码
function loadScript(url, callback) {
  const script = document.createElement('script');
  script.src = url;
  
  script.onload = function() {
    callback(null, script);
  };
  
  script.onerror = function() {
    callback(new Error(`Failed to load script ${url}`), script);
  };
  
  document.head.appendChild(script);
}

// 使用示例
loadScript('script.js', function(err, script) {
  if (err) {
    console.error(err);
  } else {
    console.log('Script loaded successfully');
  }
});

3. 并行加载与顺序执行

有时我们需要按特定顺序加载多个脚本,但希望并行下载以提高性能:

javascript 复制代码
function loadScripts(scripts, callback) {
  let loaded = 0;
  const head = document.head;
  
  scripts.forEach((url, index) => {
    const script = document.createElement('script');
    script.src = url;
    
    // 确保脚本按顺序执行
    script.async = false;
    
    script.onload = script.onreadystatechange = function() {
      if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {
        loaded++;
        if (loaded === scripts.length) {
          callback();
        }
      }
    };
    
    head.appendChild(script);
  });
}

// 使用示例
loadScripts(['script1.js', 'script2.js', 'script3.js'], function() {
  console.log('All scripts loaded');
});

4. 使用Promise的现代方式

ES6引入Promise后,我们可以用更优雅的方式处理异步加载:

javascript 复制代码
function loadScript(url) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = url;
    
    script.onload = () => resolve(script);
    script.onerror = () => reject(new Error(`Script load error for ${url}`));
    
    document.head.appendChild(script);
  });
}

// 使用示例
loadScript('script.js')
  .then(script => {
    console.log(`${script.src} is loaded`);
  })
  .catch(error => {
    console.error(error);
  });

// 或者使用async/await
(async function() {
  try {
    await loadScript('script1.js');
    await loadScript('script2.js');
    console.log('All scripts loaded');
  } catch (error) {
    console.error(error);
  }
})();

5. 动态模块加载(ES Modules)

随着ES Modules的普及,我们可以动态加载模块:

javascript 复制代码
// 动态导入模块
async function loadModule() {
  try {
    const module = await import('./module.js');
    module.doSomething();
  } catch (error) {
    console.error('Failed to load module', error);
  }
}

// 或者直接使用动态import语法
button.addEventListener('click', async () => {
  const module = await import('./module.js');
  module.doSomething();
});

6. 使用第三方库

一些流行的库如RequireJS、SystemJS等提供了更强大的动态加载功能:

javascript 复制代码
// 使用RequireJS
require(['module1', 'module2'], function(module1, module2) {
  // 模块加载完成后的回调
});

// 使用SystemJS
System.import('module.js')
  .then(module => {
    // 使用模块
  })
  .catch(error => {
    console.error(error);
  });

最佳实践与注意事项

  1. 缓存控制:浏览器会缓存脚本,但有时需要添加时间戳或版本号来避免缓存问题

    javascript 复制代码
    script.src = `${url}?v=${Date.now()}`;
  2. 错误处理:始终处理加载失败的情况

  3. 性能考虑:避免过多的小文件请求,考虑合并脚本

  4. 跨域问题:确保CORS策略允许加载外部脚本

  5. 文档位置:通常将脚本添加到<head><body>的末尾

动态脚本加载是现代Web应用的重要组成部分,合理使用可以显著提高应用性能和用户体验。根据具体需求选择合适的加载模式,并注意处理各种边界情况和错误状态。