動態加載 js

  • 前端項目中使用到了一個報表庫 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>;
}
相關文章
相關標籤/搜索