<span id="mktg5"></span>

<i id="mktg5"><meter id="mktg5"></meter></i>

        <label id="mktg5"><meter id="mktg5"></meter></label>
        最新文章專題視頻專題問答1問答10問答100問答1000問答2000關(guān)鍵字專題1關(guān)鍵字專題50關(guān)鍵字專題500關(guān)鍵字專題1500TAG最新視頻文章推薦1 推薦3 推薦5 推薦7 推薦9 推薦11 推薦13 推薦15 推薦17 推薦19 推薦21 推薦23 推薦25 推薦27 推薦29 推薦31 推薦33 推薦35 推薦37視頻文章20視頻文章30視頻文章40視頻文章50視頻文章60 視頻文章70視頻文章80視頻文章90視頻文章100視頻文章120視頻文章140 視頻2關(guān)鍵字專題關(guān)鍵字專題tag2tag3文章專題文章專題2文章索引1文章索引2文章索引3文章索引4文章索引5123456789101112131415文章專題3
        問答文章1 問答文章501 問答文章1001 問答文章1501 問答文章2001 問答文章2501 問答文章3001 問答文章3501 問答文章4001 問答文章4501 問答文章5001 問答文章5501 問答文章6001 問答文章6501 問答文章7001 問答文章7501 問答文章8001 問答文章8501 問答文章9001 問答文章9501
        當(dāng)前位置: 首頁 - 科技 - 知識(shí)百科 - 正文

        概述如何實(shí)現(xiàn)一個(gè)簡單的瀏覽器端js模塊加載器

        來源:懂視網(wǎng) 責(zé)編:小采 時(shí)間:2020-11-27 20:27:39
        文檔

        概述如何實(shí)現(xiàn)一個(gè)簡單的瀏覽器端js模塊加載器

        概述如何實(shí)現(xiàn)一個(gè)簡單的瀏覽器端js模塊加載器:在es6之前,js不像其他語言自帶成熟的模塊化功能,頁面只能靠插入一個(gè)個(gè)script標(biāo)簽來引入自己的或第三方的腳本,并且容易帶來命名沖突的問題。js社區(qū)做了很多努力,在當(dāng)時(shí)的運(yùn)行環(huán)境中,實(shí)現(xiàn)"模塊"的效果。通用的js模塊化標(biāo)準(zhǔn)有CommonJS與
        推薦度:
        導(dǎo)讀概述如何實(shí)現(xiàn)一個(gè)簡單的瀏覽器端js模塊加載器:在es6之前,js不像其他語言自帶成熟的模塊化功能,頁面只能靠插入一個(gè)個(gè)script標(biāo)簽來引入自己的或第三方的腳本,并且容易帶來命名沖突的問題。js社區(qū)做了很多努力,在當(dāng)時(shí)的運(yùn)行環(huán)境中,實(shí)現(xiàn)"模塊"的效果。通用的js模塊化標(biāo)準(zhǔn)有CommonJS與

        在es6之前,js不像其他語言自帶成熟的模塊化功能,頁面只能靠插入一個(gè)個(gè)script標(biāo)簽來引入自己的或第三方的腳本,并且容易帶來命名沖突的問題。js社區(qū)做了很多努力,在當(dāng)時(shí)的運(yùn)行環(huán)境中,實(shí)現(xiàn)"模塊"的效果。

        通用的js模塊化標(biāo)準(zhǔn)有CommonJS與AMD,前者運(yùn)用于node環(huán)境,后者在瀏覽器環(huán)境中由Require.js等實(shí)現(xiàn)。此外還有國內(nèi)的開源項(xiàng)目Sea.js,遵循CMD規(guī)范。(目前隨著es6的普及已經(jīng)停止維護(hù),不論是AMD還是CMD,都將是一段歷史了)

        瀏覽器端js加載器

        實(shí)現(xiàn)一個(gè)簡單的js加載器并不復(fù)雜,主要可以分為解析路徑、下載模塊、解析模塊依賴、解析模塊四個(gè)步驟。

        首先定義一下模塊。在各種規(guī)范中,通常一個(gè)js文件即表示一個(gè)模塊。那么,我們可以在模塊文件中,構(gòu)造一個(gè)閉包,并傳出一個(gè)對(duì)象,作為模塊的導(dǎo)出:

        define(factory() {
         var x = {
         a: 1
         };
         return x;
        });

        define函數(shù)接收一個(gè)工廠函數(shù)參數(shù),瀏覽器執(zhí)行該腳本時(shí),define函數(shù)執(zhí)行factory,并把它的return值存儲(chǔ)在加載器的模塊對(duì)象modules里。

        如何標(biāo)識(shí)一個(gè)模塊呢?可以用文件的uri,它是唯一標(biāo)識(shí),是天然的id。

        文件路徑path有幾種形式:

        絕對(duì)路徑:http://xxx, file://xxx

        相對(duì)路徑:./xxx , ../xxx , xxx(相對(duì)當(dāng)前頁面的文件路徑)

        虛擬絕對(duì)路徑:/xxx /表示網(wǎng)站根目錄

        因此,需要一個(gè)resolvePath函數(shù)來將不同形式的path解析成uri,參照當(dāng)前頁面的文件路徑來解析。

        接著,假設(shè)我們需要引用a.js與b.js兩個(gè)模塊,并設(shè)置了需要a與b才能執(zhí)行的回調(diào)函數(shù)f。我們希望加載器去拉取a與b,當(dāng)a與b都加載完成后,從modules里取出a與b作為參數(shù)傳給f,執(zhí)行下一步操作。這里可以用觀察者模式(即訂閱/發(fā)布模式)實(shí)現(xiàn),創(chuàng)建一個(gè)eventProxy,訂閱加載a與加載b事件;define函數(shù)執(zhí)行到最后,已經(jīng)把導(dǎo)出掛載modules里之后,emit一個(gè)本模塊加載完成的事件,eventProxy收到后檢查a與b是否都加載完成,如果完成,就傳參給f執(zhí)行回調(diào)。

        同理,eventProxy也可以實(shí)現(xiàn)模塊依賴加載

        // a.js
        define([ 'c.js', 'd.js' ], factory (c, d) {
         var x = c + d;
         return x;
        });

        define函數(shù)的第一個(gè)參數(shù)可以傳入一個(gè)依賴數(shù)組,表示a模塊依賴c與d。define執(zhí)行時(shí),告訴eventProxy訂閱c與d加載事件,加載好了就執(zhí)行回調(diào)函數(shù)f存儲(chǔ)a的導(dǎo)出,并emit事件a已加載。

        瀏覽器端加載腳本的原始方法是插入一個(gè)script標(biāo)簽,指定src之后,瀏覽器開始下載該腳本。

        那么加載器中的模塊加載可以用dom操作實(shí)現(xiàn),插入一個(gè)script標(biāo)簽并指定src,此時(shí)該模塊為下載中狀態(tài)。

        PS:瀏覽器中,動(dòng)態(tài)插入script標(biāo)簽與初次加載頁面dom時(shí)的script加載方式不同:

        初次加載頁面,瀏覽器會(huì)從上到下順序解析dom,碰到script標(biāo)簽時(shí),下載腳本并阻塞dom解析,等到該腳本下載、執(zhí)行完畢后再繼續(xù)解析之后的dom(現(xiàn)代瀏覽器做了preload優(yōu)化,會(huì)預(yù)先下載好多個(gè)腳本,但執(zhí)行順序與它們?cè)赿om中順序一致,執(zhí)行時(shí)阻塞其他dom解析)

        動(dòng)態(tài)插入script,

        var a = document.createElement('script'); a.src='xxx'; document.body.appendChild(a);

        瀏覽器會(huì)在該腳本下載完成后執(zhí)行,過程是異步的。

        下載完成后執(zhí)行上述的操作,解析依賴->加載依賴->解析本模塊->加載完成->執(zhí)行回調(diào)。

        模塊下載完成后,如何在解析它時(shí)知道它的uri呢?有兩種發(fā)發(fā),一種是用srcipt.onload獲取this對(duì)象的src屬性;一種是在define函數(shù)中采用document.currentScript.src。

        實(shí)現(xiàn)基本的功能比較簡單,代碼不到200行:

        var zmm = {
         _modules: {},
         _configs: {
         // 用于拼接相對(duì)路徑
         basePath: (function (path) {
         if (path.charAt(path.length - 1) === '/') {
         path = path.substr(0, path.length - 1);
         }
         return path.substr(path.indexOf(location.host) + location.host.length + 1);
         })(location.href),
         // 用于拼接相對(duì)根路徑
         host: location.protocol + '//' + location.host + '/'
         }
        };
        zmm.hasModule = function (_uri) {
         // 判斷是否已有該模塊,不論加載中或已加載好
         return this._modules.hasOwnProperty(_uri);
        };
        zmm.isModuleLoaded = function (_uri) {
         // 判斷該模塊是否已加載好
         return !!this._modules[_uri];
        };
        zmm.pushModule = function (_uri) {
         // 新模塊占坑,但此時(shí)還未加載完成,表示加載中;防止重復(fù)加載
         if (!this._modules.hasOwnProperty(_uri)) {
         this._modules[_uri] = null;
         }
        };
        zmm.installModule = function (_uri, mod) {
         this._modules[_uri] = mod;
        };
        zmm.load = function (uris) {
         var i, nsc;
         for (i = 0; i < uris.length; i++) {
         if (!this.hasModule(uris[i])) {
         this.pushModule(uris[i]);
         // 開始加載
         var nsc = document.createElement('script');
         nsc.src = uri;
         document.body.appendChild(nsc);
         }
         }
        };
        zmm.resolvePath = function (path) {
         // 返回絕對(duì)路徑
         var res = '', paths = [], resPaths;
         if (path.match(/.*:\/\/.*/)) {
         // 絕對(duì)路徑
         res = path.match(/.*:\/\/.*?\//)[0]; // 協(xié)議+域名
         path = path.substr(res.length);
         } else if (path.charAt(0) === '/') {
         // 相對(duì)根路徑 /開頭
         res = this._configs.host;
         path = path.substr(1);
         } else {
         // 相對(duì)路徑 ./或../開頭或直接文件名
         res = this._configs.host;
         resPaths = this._configs.basePath.split('/');
         }
         resPaths = resPaths || [];
         paths = path.split('/');
         for (var i = 0; i < paths.length; i++) {
         if (paths[i] === '..') {
         resPaths.pop();
         } else if (paths[i] === '.') {
         // do nothing
         } else {
         resPaths.push(paths[i]);
         }
         }
         res += resPaths.join('/');
         return res;
        };
        var define = zmm.define = function (dependPaths, fac) {
         var _uri = document.currentScript.src;
         if (zmm.isModuleLoaded(_uri)) {
         return;
         }
         var factory, depPaths, uris = [];
         if (arguments.length === 1) {
         factory = arguments[0];
         // 掛載到模塊組中
         zmm.installModule(_uri, factory());
         // 告訴proxy該模塊已裝載好
         zmm.proxy.emit(_uri);
         } else {
         // 有依賴的情況
         factory = arguments[1];
         // 裝載完成的回調(diào)函數(shù)
         zmm.use(arguments[0], function () {
         zmm.installModule(_uri, factory.apply(null, arguments));
         zmm.proxy.emit(_uri);
         });
         }
        };
        zmm.use = function (paths, callback) {
         if (!Array.isArray(paths)) {
         paths = [paths];
         }
         var uris = [], i;
         for (i = 0; i < paths.length; i++) {
         uris.push(this.resolvePath(paths[i]));
         }
         // 先注冊(cè)事件,再加載
         this.proxy.watch(uris, callback);
         this.load(uris);
        };
        zmm.proxy = function () {
         var proxy = {};
         var taskId = 0;
         var taskList = {};
         var execute = function (task) {
         var uris = task.uris,
         callback = task.callback;
         for (var i = 0, arr = []; i < uris.length; i++) {
         arr.push(zmm._modules[uris[i]]);
         }
         callback.apply(null, arr);
         };
         var deal_loaded = function (_uri) {
         var i, k, task, sum;
         // 當(dāng)一個(gè)模塊加載完成時(shí),遍歷當(dāng)前任務(wù)棧
         for (k in taskList) {
         if (!taskList.hasOwnProperty(k)) {
         continue;
         }
         task = taskList[k];
         if (task.uris.indexOf(_uri) > -1) {
         // 查看這個(gè)任務(wù)中的模塊是否都已加載好
         for (i = 0, sum = 0; i < task.uris.length; i++) {
         if (zmm.isModuleLoaded(task.uris[i])) {
         sum ++;
         }
         }
         if (sum === task.uris.length) {
         // 都加載完成 刪除任務(wù)
         delete(taskList[k]);
         execute(task);
         }
         }
         }
         };
         proxy.watch = function (uris, callback) {
         // 先檢查一遍是否都加載好了
         for (var i = 0, sum = 0; i < uris.length; i++) {
         if (zmm.isModuleLoaded(uris[i])) {
         sum ++;
         }
         }
         if (sum === uris.length) {
         execute({
         uris: uris,
         callback: callback
         });
         } else {
         // 訂閱新加載任務(wù)
         var task = {
         uris: uris,
         callback: callback
         };
         taskList['' + taskId] = task;
         taskId ++;
         }
         };
         proxy.emit = function (_uri) {
         console.log(_uri + ' is loaded!');
         deal_loaded(_uri);
         };
         return proxy;
        }();

        循環(huán)依賴問題

        "循環(huán)加載"指的是,a腳本的執(zhí)行依賴b腳本,而b腳本的執(zhí)行又依賴a腳本。這是一種應(yīng)該盡量避免的設(shè)計(jì)。

        瀏覽器端

        用上面的zmm工具加載模塊a:

        // main.html
        zmm.use('/a.js', function(){...});
        // a.js
        define('/b.js', function(b) {
         var a = 1;
         a = b + 1;
         return a;
        });
        // b.js
        define('/a.js', function(a) {
         var b = a + 1;
         return b;
        });

        就會(huì)陷入a等待b加載完成、b等待a加載完成的死鎖狀態(tài)。sea.js碰到這種情況也是死鎖,也許是默認(rèn)這種行為不應(yīng)該出現(xiàn)。

        seajs里可以通過require.async來緩解循環(huán)依賴的問題,但必須改寫a.js:

        // a.js
        define('./js/a', function (require, exports, module) {
         var a = 1;
         require.async('./b', function (b) {
         a = b + 1;
         module.exports = a; //a= 3
         });
         module.exports = a; // a= 1
        });
        // b.js
        define('./js/b', function (require, exports, module) {
         var a = require('./a');
         var b = a + 1;
         module.exports = b;
        });
        // main.html
        seajs.use('./js/a', function (a) {
         console.log(a); // 1
        });

        但這么做a就必須先知道b會(huì)依賴自己,且use中輸出的是b還沒加載時(shí)a的值,use并不知道a的值之后還會(huì)改變。

        在瀏覽器端,似乎沒有很好的解決方案。node模塊加載碰到的循環(huán)依賴問題則小得多。

        node/CommonJS

        CommonJS模塊的重要特性是加載時(shí)執(zhí)行,即腳本代碼在require的時(shí)候,就會(huì)全部執(zhí)行。CommonJS的做法是,一旦出現(xiàn)某個(gè)模塊被"循環(huán)加載",就只輸出已經(jīng)執(zhí)行的部分,還未執(zhí)行的部分不會(huì)輸出。

        // a.js
        var a = 1;
        module.exports = a;
        var b = require('./b');
        a = b + 1;
        module.exports = a;
        // b.js
        var a = require('./a');
        var b = a + 1;
        module.exports = b;
        // main.js
        var a = require('./a');
        console.log(a); //3

        上面main.js的代碼中,先加載模塊a,執(zhí)行require函數(shù),此時(shí)內(nèi)存中已經(jīng)掛了一個(gè)模塊a,它的exports為一個(gè)空對(duì)象a.exports={};接著執(zhí)行a.js中的代碼;執(zhí)行var b = require('./b');之前,a.exports=1,接著執(zhí)行require(b);b.js被執(zhí)行時(shí),拿到的是a.exports=1,b加載完成后,執(zhí)行權(quán)回到a.js;最后a模塊的輸出為3。

        CommonJS與瀏覽器端的加載器有著實(shí)現(xiàn)上的差異。node加載的模塊都是在本地,執(zhí)行的是同步的加載過程,即按依賴關(guān)系依次加載,執(zhí)行到加載語句就去加載另一個(gè)模塊,加載完了再回到函數(shù)調(diào)用點(diǎn)繼續(xù)執(zhí)行;瀏覽器端加載scripts由于天生限制,只能采取異步加載,執(zhí)行回調(diào)來實(shí)現(xiàn)。

        ES6

        ES6模塊的運(yùn)行機(jī)制與CommonJS不一樣,它遇到模塊加載命令import時(shí),不會(huì)去執(zhí)行模塊,而是只生成一個(gè)引用。等到真的需要用到時(shí),再到模塊里面去取值。因此,ES6模塊是動(dòng)態(tài)引用,不存在緩存值的問題,而且模塊里面的變量,綁定其所在的模塊。

        這導(dǎo)致ES6處理"循環(huán)加載"與CommonJS有本質(zhì)的不同。ES6根本不會(huì)關(guān)心是否發(fā)生了"循環(huán)加載",只是生成一個(gè)指向被加載模塊的引用,需要開發(fā)者自己保證,真正取值的時(shí)候能夠取到值。

        來看一個(gè)例子:

        // even.js
        import { odd } from './odd';
        export var counter = 0;
        export function even(n) { counter++; return n == 0 || odd(n - 1);}
        // odd.js
        import { even } from './even';
        export function odd(n) { return n != 0 && even(n - 1);}
        // main.js
        import * as m from './even.js';
        m.even(10); // true; m.counter = 6

        上面代碼中,even.js里面的函數(shù)even有一個(gè)參數(shù)n,只要不等于0,就會(huì)減去1,傳入加載的odd()。odd.js也會(huì)做類似作。

        上面代碼中,參數(shù)n從10變?yōu)?的過程中,foo()一共會(huì)執(zhí)行6次,所以變量counter等于6。第二次調(diào)用even()時(shí),參數(shù)n從20變?yōu)?,foo()一共會(huì)執(zhí)行11次,加上前面的6次,所以變量counter等于17。

        而這個(gè)例子要是改寫成CommonJS,就根本無法執(zhí)行,會(huì)報(bào)錯(cuò)。

        // even.js
        var odd = require('./odd');
        var counter = 0;
        exports.counter = counter;
        exports.even = function(n) {
        counter++;
        return n == 0 || odd(n - 1);
        }
        // odd.js
        var even = require('./even').even;
        module.exports = function(n) {
        return n != 0 && even(n - 1);
        }
        // main.js
        var m = require('./even');
        m.even(10); // TypeError: even is not a function

        上面代碼中,even.js加載odd.js,而odd.js又去加載even.js,形成"循環(huán)加載"。這時(shí),執(zhí)行引擎就會(huì)輸出even.js已經(jīng)執(zhí)行的部分(不存在任何結(jié)果),所以在odd.js之中,變量even等于null,等到后面調(diào)用even(n-1)就會(huì)報(bào)錯(cuò)。

        聲明:本網(wǎng)頁內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com

        文檔

        概述如何實(shí)現(xiàn)一個(gè)簡單的瀏覽器端js模塊加載器

        概述如何實(shí)現(xiàn)一個(gè)簡單的瀏覽器端js模塊加載器:在es6之前,js不像其他語言自帶成熟的模塊化功能,頁面只能靠插入一個(gè)個(gè)script標(biāo)簽來引入自己的或第三方的腳本,并且容易帶來命名沖突的問題。js社區(qū)做了很多努力,在當(dāng)時(shí)的運(yùn)行環(huán)境中,實(shí)現(xiàn)"模塊"的效果。通用的js模塊化標(biāo)準(zhǔn)有CommonJS與
        推薦度:
        標(biāo)簽: 簡單 實(shí)現(xiàn) js
        • 熱門焦點(diǎn)

        最新推薦

        猜你喜歡

        熱門推薦

        專題
        Top
        主站蜘蛛池模板: 亚洲成av人影院| 亚洲高清免费在线观看| 国产自偷亚洲精品页65页| 亚洲精品视频专区| 一级毛片aa高清免费观看| 久久成人国产精品免费软件| 免费在线观看黄网| 亚洲男人天堂2018av| 久久免费观看国产精品88av| 免费成人黄色大片| 黄网站色视频免费看无下截 | 亚洲色精品VR一区区三区| 成人特黄a级毛片免费视频| 亚洲中文久久精品无码| 爱丫爱丫影院在线观看免费| 免费a级黄色毛片| yellow视频免费在线观看| 亚洲欧洲自拍拍偷午夜色无码| 综合偷自拍亚洲乱中文字幕| 免费看美女裸露无档网站| 亚洲第一中文字幕| 99精品全国免费观看视频..| 夜色阁亚洲一区二区三区| 亚洲AV无码一区二区三区电影| 99精品一区二区免费视频| 亚洲尤码不卡AV麻豆| 99精品在线免费观看| 亚洲私人无码综合久久网| 日本免费一区二区在线观看| 亚洲精品中文字幕无码A片老| 无码区日韩特区永久免费系列 | 久久精品国产亚洲AV果冻传媒| 无码AV动漫精品一区二区免费| 国产视频精品免费| 亚洲av无码偷拍在线观看| 久久亚洲av无码精品浪潮| 乱淫片免费影院观看| 亚洲影院在线观看| 亚洲精品视频免费看| 亚洲不卡在线观看| 午夜小视频免费观看|