动态加载 js

更新于 2018-06-10  约 8 分钟
  • 前端项目中使用到了一个报表库 Plotly.js, 这个库有 600多k。由于报表只有部分模块用到,所以想使用动态加载方式。
  • 首先想到使用 webpack 的懒加载,但是打包时间太长。加这个库之前 30秒,加之后 6 分钟。使用 noParse 也没有效果。
  • 所以决定用到时,手动加载。
  • js 常用的动态加载有两种。ajax 加载后使用 eval 执行。或者使用 script 标签加载
  • 这里介绍动态创建标签的方法。不说了,直接上代码:
// Attach handlers for all browsers


var loadScript = function (path, callback) {
    const me = this;
    const script = document.createElement('script');
    script.type = 'text/javascript';
    let done = false;
    const head = document.head;
    const error = () => {
        if (!done) {
            done = true;
            script.onload = script.onreadystatechange = script.onerror = null;
            head.removeChild(script);
            callback(false);
        }
    }
    const success = () => {
        if (!done) {
            done = true;
            script.onload = script.onreadystatechange = script.onerror = null;
            head.removeChild(script);
            callback(true);
        }
    }
    script.onreadystatechange = function () {
        if (this.readyState == "loaded" || this.readyState == "complete") {
            success();
        }
    };
    script.onload = success;
    script.onerror = error;
    script.src = path;
    head.appendChild(script);
}
  • 上面是动态加载 js 的方法。但是可能多个模块会用到这个 js 库。当同时打开这些模块时,有可能会导致多次加载。所以可以把加载后的模块根据路径缓存起来。
  • 下面代码是使用 TypeScript 写的, 根据路径记录 js 文件加载信息,避免重复:
export default class LoadJs {

    public static loadSync(path: string): Promise<void> {
        const me = this;
        return new Promise((resolve, reject) => {
            me.load(path, (bSuc) => {
                if (bSuc) {
                    resolve();
                } else {
                    reject();
                }
            });
        });
    }

    public static load(path: string, callback: (bSuc: boolean) => void): void {
        let lib = this.pathLoaded[path];
        // 没有记录,添加记录
        if (!lib) {
            lib = {
                isLoaded: false,
                callback: [callback],
            };
            this.pathLoaded[path] = lib;
        }
        // 已加载直接返回
        if (lib.isLoaded) {
            callback(true);
        } else {
            // 添加回调
            lib.callback.push(callback);
            // 加载
            const me = this;
            this.loadScript(path, suc => {
                if (suc) {
                    me.onLoad(lib, true);
                } else {
                    me.onLoad(lib, false);
                }
            })
        }
    }

    private static loadScript(path, callback) {
        const me = this;
        const script = document.createElement('script') as any;
        script.type = 'text/javascript';
        let done = false;
        const head = document.head;
        const error = () => {
            if (!done) {
                done = true;
                script.onload = script.onreadystatechange = script.onerror = null;
                head.removeChild(script);
                callback(false);
            }
        }
        const success = () => {
            if (!done) {
                done = true;
                script.onload = script.onreadystatechange = script.onerror = null;
                head.removeChild(script);
                callback(true);
            }
        }
        script.onreadystatechange = function () {
            if (this.readyState == "loaded" || this.readyState == "complete") {
                success();
            }
        };
        script.onload = success;
        script.onerror = error;
        script.src = path;
        head.appendChild(script);
    }
    private static pathLoaded: { [key: string]: PathLoading } = {};
    private static onLoad(p: PathLoading, isSuc: boolean): void {
        p.isLoaded = true;
        for (const fun of p.callback) {
            fun(isSuc);
        }
        p.callback = [];
    }
}

interface PathLoading {
    isLoaded: boolean;
    callback: Array<(f: boolean) => void>;
}
阅读 3k更新于 2018-06-10

推荐阅读
目录