Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

动态导入

动态导入(Dynamic Import)是ES2020引入的功能,允许在运行时按需加载模块。与静态导入不同,动态导入提供了更大的灵活性,支持条件加载、懒加载和代码分割等高级用法。

基本语法

import() 函数

// 基本动态导入语法
import('./module.js')
    .then(module => {
        // 使用导入的模块
        console.log(module.default);
        console.log(module.namedExport);
    })
    .catch(err => {
        console.error('Failed to load module:', err);
    });

// 使用 async/await
async function loadModule() {
    try {
        const module = await import('./module.js');
        return module;
    } catch (error) {
        console.error('Module loading failed:', error);
        throw error;
    }
}

顶层 await 与动态导入

// top-level-await.js
// 在支持顶层await的环境中使用

// 条件性加载模块
const isDevelopment = process.env.NODE_ENV === 'development';

if (isDevelopment) {
    const devTools = await import('./dev-tools.js');
    devTools.setup();
    console.log('Development tools loaded');
}

// 动态选择实现
const preferredImplementation = getPreferredImplementation();
const implementation = await import(`./implementations/${preferredImplementation}.js`);

export default implementation.default;

动态导入的应用场景

1. 条件加载

// conditional-loading.js

// 根据用户权限加载不同的模块
async function loadUserInterface(userRole) {
    switch (userRole) {
        case 'admin':
            const adminUI = await import('./admin-ui.js');
            return adminUI.default;
            
        case 'moderator':
            const modUI = await import('./moderator-ui.js');
            return modUI.default;
            
        case 'user':
        default:
            const userUI = await import('./user-ui.js');
            return userUI.default;
    }
}

// 根据功能特性加载polyfill
async function loadPolyfills() {
    const promises = [];
    
    if (!('IntersectionObserver' in window)) {
        promises.push(import('./polyfills/intersection-observer.js'));
    }
    
    if (!('fetch' in window)) {
        promises.push(import('./polyfills/fetch.js'));
    }
    
    if (!('Promise' in window)) {
        promises.push(import('./polyfills/promise.js'));
    }
    
    return Promise.all(promises);
}

// 使用示例
const user = getCurrentUser();
const ui = await loadUserInterface(user.role);
ui.render(document.body);

2. 懒加载和代码分割

// lazy-loading.js

// 路由懒加载
const routes = {
    '/': () => import('./pages/Home.js'),
    '/about': () => import('./pages/About.js'),
    '/contact': () => import('./pages/Contact.js'),
    '/dashboard': () => import('./pages/Dashboard.js'),
    '/settings': () => import('./pages/Settings.js')
};

async function navigateTo(path) {
    const loadPage = routes[path];
    
    if (!loadPage) {
        throw new Error(`No route found for ${path}`);
    }
    
    try {
        const pageModule = await loadPage();
        const page = new pageModule.default();
        
        // 清理当前页面
        clearCurrentPage();
        
        // 渲染新页面
        page.render(document.getElementById('app'));
        
        // 更新浏览器历史
        history.pushState({}, '', path);
    } catch (error) {
        console.error(`Failed to load page ${path}:`, error);
        showErrorPage();
    }
}

// 组件懒加载
class ComponentLoader {
    constructor() {
        this.cache = new Map();
    }
    
    async loadComponent(name) {
        // 检查缓存
        if (this.cache.has(name)) {
            return this.cache.get(name);
        }
        
        try {
            const module = await import(`./components/${name}.js`);
            const component = module.default;
            
            // 缓存组件
            this.cache.set(name, component);
            
            return component;
        } catch (error) {
            console.error(`Failed to load component ${name}:`, error);
            return null;
        }
    }
}

const loader = new ComponentLoader();

// 使用示例
document.addEventListener('click', async (event) => {
    if (event.target.dataset.component) {
        const componentName = event.target.dataset.component;
        const Component = await loader.loadComponent(componentName);
        
        if (Component) {
            const instance = new Component();
            instance.mount(event.target.parentElement);
        }
    }
});

3. 插件系统

// plugin-system.js

class PluginManager {
    constructor() {
        this.plugins = new Map();
        this.hooks = new Map();
    }
    
    // 动态加载插件
    async loadPlugin(pluginName, config = {}) {
        try {
            const pluginModule = await import(`./plugins/${pluginName}/index.js`);
            const PluginClass = pluginModule.default;
            
            const plugin = new PluginClass(config);
            
            // 初始化插件
            if (typeof plugin.init === 'function') {
                await plugin.init();
            }
            
            // 注册插件
            this.plugins.set(pluginName, plugin);
            
            // 注册插件的钩子
            if (plugin.hooks) {
                Object.entries(plugin.hooks).forEach(([hookName, handler]) => {
                    this.registerHook(hookName, handler);
                });
            }
            
            console.log(`Plugin ${pluginName} loaded successfully`);
            return plugin;
        } catch (error) {
            console.error(`Failed to load plugin ${pluginName}:`, error);
            throw error;
        }
    }
    
    // 卸载插件
    async unloadPlugin(pluginName) {
        const plugin = this.plugins.get(pluginName);
        
        if (!plugin) {
            return false;
        }
        
        // 执行清理
        if (typeof plugin.destroy === 'function') {
            await plugin.destroy();
        }
        
        // 移除钩子
        if (plugin.hooks) {
            Object.keys(plugin.hooks).forEach(hookName => {
                this.unregisterHook(hookName, plugin.hooks[hookName]);
            });
        }
        
        this.plugins.delete(pluginName);
        console.log(`Plugin ${pluginName} unloaded`);
        return true;
    }
    
    registerHook(hookName, handler) {
        if (!this.hooks.has(hookName)) {
            this.hooks.set(hookName, []);
        }
        this.hooks.get(hookName).push(handler);
    }
    
    unregisterHook(hookName, handler) {
        const handlers = this.hooks.get(hookName);
        if (handlers) {
            const index = handlers.indexOf(handler);
            if (index > -1) {
                handlers.splice(index, 1);
            }
        }
    }
    
    // 触发钩子
    async triggerHook(hookName, ...args) {
        const handlers = this.hooks.get(hookName) || [];
        const results = await Promise.all(
            handlers.map(handler => handler(...args))
        );
        return results;
    }
}

// 使用示例
const pluginManager = new PluginManager();

// 加载插件
await pluginManager.loadPlugin('analytics', {
    trackingId: 'GA-XXXXXX-X'
});

await pluginManager.loadPlugin('chat-widget', {
    apiKey: 'chat-api-key',
    position: 'bottom-right'
});

// 触发钩子
await pluginManager.triggerHook('user-login', { userId: 123 });

4. 国际化和本地化

// i18n.js

class I18nManager {
    constructor(defaultLocale = 'en') {
        this.currentLocale = defaultLocale;
        this.translations = new Map();
        this.fallbacks = new Map();
    }
    
    // 动态加载语言包
    async loadLocale(locale) {
        if (this.translations.has(locale)) {
            return this.translations.get(locale);
        }
        
        try {
            // 尝试加载完整语言包
            const fullModule = await import(`./locales/${locale}.js`);
            this.translations.set(locale, fullModule.default);
            return fullModule.default;
        } catch (error) {
            // 如果失败,尝试加载语言的基础版本
            const baseLang = locale.split('-')[0];
            if (baseLang !== locale) {
                try {
                    const baseModule = await import(`./locales/${baseLang}.js`);
                    this.fallbacks.set(locale, baseModule.default);
                    return baseModule.default;
                } catch (baseError) {
                    console.error(`Failed to load locale ${locale} and ${baseLang}:`, error, baseError);
                }
            }
            throw error;
        }
    }
    
    // 设置当前语言
    async setLocale(locale) {
        await this.loadLocale(locale);
        this.currentLocale = locale;
        
        // 触发语言变更事件
        document.dispatchEvent(new CustomEvent('locale-changed', {
            detail: { locale, translations: this.translations.get(locale) }
        }));
    }
    
    // 获取翻译
    t(key, params = {}) {
        const translations = this.translations.get(this.currentLocale) ||
                           this.fallbacks.get(this.currentLocale) ||
                           this.translations.get('en');
        
        if (!translations) {
            return key;
        }
        
        let text = this.getNestedValue(translations, key) || key;
        
        // 替换参数
        Object.entries(params).forEach(([param, value]) => {
            text = text.replace(new RegExp(`\\{\\{${param}\\}\\}`, 'g'), value);
        });
        
        return text;
    }
    
    getNestedValue(obj, path) {
        return path.split('.').reduce((current, key) => {
            return current && current[key] !== undefined ? current[key] : null;
        }, obj);
    }
}

// 使用示例
const i18n = new I18nManager('en');

// 检测用户语言并加载
const userLocale = navigator.language || navigator.userLanguage || 'en';
await i18n.setLocale(userLocale);

// 在应用中使用
console.log(i18n.t('welcome.message', { name: 'John' }));
console.log(i18n.t('navigation.home'));

// 语言切换器
async function switchLanguage(locale) {
    try {
        await i18n.setLocale(locale);
        updateUI();
    } catch (error) {
        console.error('Failed to switch language:', error);
    }
}

动态导入的高级用法

1. 并行加载多个模块

// parallel-loading.js

// 并行加载多个相关模块
async function loadDashboardModules() {
    const [
        chartsModule,
        tablesModule,
        filtersModule,
        exportModule
    ] = await Promise.all([
        import('./charts.js'),
        import('./tables.js'),
        import('./filters.js'),
        import('./export.js')
    ]);
    
    return {
        Charts: chartsModule.default,
        Tables: tablesModule.default,
        Filters: filtersModule.default,
        Export: exportModule.default
    };
}

// 使用Promise.allSettled处理部分失败
async function loadOptionalModules() {
    const results = await Promise.allSettled([
        import('./analytics.js'),
        import('./chat.js'),
        import('./notifications.js'),
        import('./help.js')
    ]);
    
    const loadedModules = {};
    
    results.forEach((result, index) => {
        const moduleNames = ['analytics', 'chat', 'notifications', 'help'];
        const moduleName = moduleNames[index];
        
        if (result.status === 'fulfilled') {
            loadedModules[moduleName] = result.value.default;
            console.log(`${moduleName} module loaded successfully`);
        } else {
            console.warn(`${moduleName} module failed to load:`, result.reason);
        }
    });
    
    return loadedModules;
}

2. 模块预加载

// preloading.js

class ModulePreloader {
    constructor() {
        this.preloadCache = new Map();
        this.loadingPromises = new Map();
    }
    
    // 预加载模块(但不执行)
    preload(modulePath) {
        if (this.preloadCache.has(modulePath)) {
            return this.preloadCache.get(modulePath);
        }
        
        // 创建link标签进行预加载
        const link = document.createElement('link');
        link.rel = 'modulepreload';
        link.href = modulePath;
        document.head.appendChild(link);
        
        const promise = import(modulePath).then(module => {
            this.preloadCache.set(modulePath, module);
            return module;
        });
        
        this.loadingPromises.set(modulePath, promise);
        return promise;
    }
    
    // 立即获取预加载的模块
    async getPreloaded(modulePath) {
        if (this.preloadCache.has(modulePath)) {
            return this.preloadCache.get(modulePath);
        }
        
        if (this.loadingPromises.has(modulePath)) {
            return this.loadingPromises.get(modulePath);
        }
        
        return import(modulePath);
    }
    
    // 批量预加载
    preloadBatch(modulePaths) {
        return Promise.all(modulePaths.map(path => this.preload(path)));
    }
    
    // 智能预加载:根据用户行为预测
    intelligentPreload(userBehavior) {
        const predictions = this.predictNextModules(userBehavior);
        return this.preloadBatch(predictions);
    }
    
    predictNextModules(behavior) {
        // 简单的预测逻辑
        const moduleMap = {
            'viewing-products': ['./cart.js', './checkout.js'],
            'in-cart': ['./payment.js', './shipping.js'],
            'profile-page': ['./settings.js', './orders.js']
        };
        
        return moduleMap[behavior] || [];
    }
}

const preloader = new ModulePreloader();

// 在应用启动时预加载关键模块
preloader.preloadBatch([
    './router.js',
    './auth.js',
    './api-client.js'
]);

// 根据用户行为智能预加载
document.addEventListener('mouseover', (event) => {
    if (event.target.dataset.preload) {
        preloader.preload(event.target.dataset.preload);
    }
});

3. 容错和重试机制

// error-handling.js

class RobustModuleLoader {
    constructor(options = {}) {
        this.maxRetries = options.maxRetries || 3;
        this.retryDelay = options.retryDelay || 1000;
        this.fallbacks = options.fallbacks || {};
    }
    
    async loadWithRetry(modulePath, retryCount = 0) {
        try {
            return await import(modulePath);
        } catch (error) {
            console.warn(`Failed to load ${modulePath} (attempt ${retryCount + 1}):`, error);
            
            if (retryCount < this.maxRetries) {
                // 指数退避
                const delay = this.retryDelay * Math.pow(2, retryCount);
                await this.sleep(delay);
                return this.loadWithRetry(modulePath, retryCount + 1);
            }
            
            // 尝试使用fallback
            const fallbackPath = this.fallbacks[modulePath];
            if (fallbackPath && fallbackPath !== modulePath) {
                console.log(`Trying fallback for ${modulePath}: ${fallbackPath}`);
                return this.loadWithRetry(fallbackPath);
            }
            
            throw new Error(`Failed to load module ${modulePath} after ${this.maxRetries} retries`);
        }
    }
    
    async loadWithFallback(primaryPath, fallbackPath) {
        try {
            return await import(primaryPath);
        } catch (error) {
            console.warn(`Primary module ${primaryPath} failed, using fallback:`, error);
            return import(fallbackPath);
        }
    }
    
    sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
}

// 使用示例
const loader = new RobustModuleLoader({
    maxRetries: 3,
    retryDelay: 1000,
    fallbacks: {
        './advanced-charts.js': './basic-charts.js',
        './high-res-images.js': './low-res-images.js'
    }
});

try {
    const chartsModule = await loader.loadWithRetry('./advanced-charts.js');
    console.log('Advanced charts loaded successfully');
} catch (error) {
    console.error('All chart loading attempts failed:', error);
}

性能考虑

1. 模块大小优化

// module-optimization.js

// 分割大模块为小块
// 代替一个大的utils模块:
// import * as utils from './large-utils.js';

// 使用按需导入:
const stringUtils = () => import('./utils/string.js');
const arrayUtils = () => import('./utils/array.js');
const dateUtils = () => import('./utils/date.js');

// 只在需要时加载具体功能
async function formatUserData(userData) {
    const { format } = await import('./utils/string.js');
    const { sortBy } = await import('./utils/array.js');
    const { formatDate } = await import('./utils/date.js');
    
    return {
        name: format(userData.name),
        dates: userData.dates.map(formatDate),
        sorted: sortBy(userData.items, 'priority')
    };
}

2. 缓存策略

// caching.js

class ModuleCache {
    constructor(options = {}) {
        this.cache = new Map();
        this.maxAge = options.maxAge || 300000; // 5分钟
        this.maxSize = options.maxSize || 50;
    }
    
    async loadModule(modulePath) {
        const cacheKey = modulePath;
        const cached = this.cache.get(cacheKey);
        
        if (cached && this.isValid(cached)) {
            return cached.module;
        }
        
        const module = await import(modulePath);
        
        this.cache.set(cacheKey, {
            module,
            timestamp: Date.now()
        });
        
        this.cleanup();
        return module;
    }
    
    isValid(cached) {
        return Date.now() - cached.timestamp < this.maxAge;
    }
    
    cleanup() {
        if (this.cache.size <= this.maxSize) {
            return;
        }
        
        // 移除最旧的条目
        const entries = Array.from(this.cache.entries());
        entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
        
        const toRemove = entries.slice(0, this.cache.size - this.maxSize);
        toRemove.forEach(([key]) => this.cache.delete(key));
    }
    
    clear() {
        this.cache.clear();
    }
}

const moduleCache = new ModuleCache({ maxAge: 600000, maxSize: 100 });

动态导入的局限性

1. 静态分析限制

// limitations.js

// ❌ 这些用法会使打包工具无法进行静态分析
const moduleName = getUserPreference();
import(moduleName); // 完全动态的路径

const modules = ['a', 'b', 'c'];
modules.forEach(name => import(name)); // 循环中的动态导入

// ✅ 更好的做法:使用部分静态路径
const theme = getUserTheme();
import(`./themes/${theme}.js`); // 部分静态路径

// ✅ 或使用显式的模块映射
const moduleMap = {
    theme1: () => import('./themes/theme1.js'),
    theme2: () => import('./themes/theme2.js'),
    theme3: () => import('./themes/theme3.js')
};

const themeLoader = moduleMap[theme];
if (themeLoader) {
    themeLoader().then(module => {
        // 使用主题模块
    });
}

2. 错误处理的重要性

// error-handling-best-practices.js

// ❌ 没有错误处理
import('./module.js').then(module => {
    module.default(); // 如果模块加载失败,这里会出错
});

// ✅ 完善的错误处理
async function loadAndUseModule() {
    try {
        const module = await import('./module.js');
        
        if (!module || !module.default) {
            throw new Error('Module does not have expected exports');
        }
        
        return module.default();
    } catch (error) {
        console.error('Module loading or execution failed:', error);
        
        // 提供fallback逻辑
        return useDefaultBehavior();
    }
}

function useDefaultBehavior() {
    // 默认行为实现
    console.log('Using default behavior due to module loading failure');
}

总结

动态导入为JavaScript模块系统带来了强大的运行时灵活性:

  • 按需加载: 减少初始包大小,提升应用启动速度
  • 条件加载: 根据用户环境、权限等条件加载不同模块
  • 代码分割: 自动将代码分割为更小的块
  • 插件系统: 支持运行时扩展应用功能
  • 懒加载: 延迟加载非关键功能
  • 国际化: 动态加载语言包和地区化内容

动态导入是现代Web应用性能优化和架构设计的重要工具,合理使用能够显著提升用户体验和应用的可维护性。


下一章: 模块解析机制