一个简单轻量的模块加载实现

230 阅读2分钟

简单介绍

  • 这是一个简单的、轻量的加载模块的方法。

使用方法

  • 输出load方法用来加载组件,参数为key(模块名)与src(模块脚本)来加载脚本,并返回一个Promise实例,then中回调的参数即为加载的组件。
  • 输出output方法用来定义组件,在组件脚本中使用,参数名为一个回调函数,回调函数返回的值为输出的组件。

实现方法

  1. 设定两个用于缓存数据的内部对象_loadPromises与_loadModules;
  2. 定义load方法,其中有两个参数key和src,key为模块的唯一标识,src为脚本的路径。
  3. load中将返回一个Promise实例,并将此Promise实例根据key来缓存到_loadPromises中,并在下次有相同的key时,直接返回此Promise实例。
  4. 在Promise实例中调用内部的_loadScript(key,src,resolve,reject)方法,其中的resolve和reject分别为Promise的resolve和reject。
  5. 在_loadScript方法中创建一个HTMLScriptElement实例,在它的onload事件处理程序中调用resolve(_loadModules[key]),在它的onerror事件处理程序中调用reject(message),并设置data-key属性为key,设置src属性为src,再将其添加到body中。
  6. 定义一个output(callback)供模块脚本中调用,通过document.currentScript元素来获取当前脚本的data-key属性值,并在_loadModules中以此属性值为属性名缓存callback回调函数的执行结果。

补充说明

  • 通过将Promise实例的resolve与reject作为值传给其他函数,就可以在其他时间段修改此Promise实例的状态。
  • 因为output方法必然在模块脚本中运行,因此可以通过document.currentScript(当前正在运行的脚本元素)来获取模块脚本上添加加的key信息,这一步是至关重要的。因为在组件脚本中只输出value值,而没有定义key值,如果没有key值,将无法确定哪个Promise实例resolve哪个value值。

源码

// 缓存load的promise对象
let _loadPromises: {
    [key: string]: Promise<any>
} = {};

// 缓存模块脚本输出的值
let _loadModules: {
    [key: string]: any;
} = {};

/**
 * 加载组件的方法
 * @param key 模块的key
 * @param src 模块脚本的路径
 */
export function load(key: string, src: string): Promise<any> {
    if (isPromise(_loadPromises[key])) {
        return _loadPromises[key];
    }
    let promise = new Promise((resolve, reject) => {
        _loadScript(key, src, resolve, reject);
    });
    // 缓存此promise对象
    _loadPromises[key] = promise;
    return promise;
}

function isPromise(promise) {
    return Object.prototype.toString.call(promise) === '[object Promise]';
}
/**
 * 加载脚本并设置属性与事件处理程序
 * @param key 模块名
 * @param src 脚本路径
 * @param resolve load方法中Promise的resolve
 * @param reject load方法中Promise的reject
 */
function _loadScript(key: string, src: string, resolve: (module: any) => void, reject: (message: string) => void): void {
    let script: HTMLScriptElement = document.createElement('script');
    script.onload = function () {
        resolve(_loadModules[key]);
    }
    script.onerror = function () {
        reject(`加载名为${key}、链接为${src}的模块失败!`);
    }
    script.src = src;
    script.async = true;
    script.setAttribute('data-key', key);
    document.body.appendChild(script);
}
/**
 * 在模块脚本调用次方法来输出模块值
 * @param callback 执行结果为模块输出值的回调函数
 */
export function output(callback: () => any): void {
    // 怎么获取模块的key呢?
    let key = document.currentScript.getAttribute('data-key');
    _loadModules[key] = callback();
}

export default { load, output };