动态加载 js

1
  • 前端项目中使用到了一个报表库 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>;
}

你可能感兴趣的

大桔子 · 2018-06-07
script.onready = () => {
        if (script.readyState === 'complete'
            || script.readyState === 'complete') {
            callback(true);
        }
    };

这个好像不会执行。。。

回复

0

这个应该是onreadystatechange,是为了兼容ie的,我这里写错了。chrome等都是走的onload方法

meteor199 作者 · 2018-06-10
imwangpan · 10月17日

请问一下楼主,head.removeChild(script); 这一步是为了什么?

回复

载入中...