摘要:想同時(shí)實(shí)現(xiàn)這些目標(biāo),就必須有一套按需加載的機(jī)制,頁(yè)面上展現(xiàn)的內(nèi)容和所有需要依賴的文件,都可以根據(jù)業(yè)務(wù)邏輯需要按需加載。最近都是基于做開發(fā),所以本文主要圍繞提供的各種機(jī)制,探索全面實(shí)現(xiàn)按需加載的套路。注意必須設(shè)置,否則變化以后,不截獲。
在進(jìn)行有一定規(guī)模的項(xiàng)目時(shí),通常希望實(shí)現(xiàn)以下目標(biāo):1、支持復(fù)雜的頁(yè)面邏輯(根據(jù)業(yè)務(wù)規(guī)則動(dòng)態(tài)展現(xiàn)內(nèi)容,例如:權(quán)限,數(shù)據(jù)狀態(tài)等);2、堅(jiān)持前后端分離的基本原則(不分離的時(shí)候,可以在后端用模版引擎直接生成好頁(yè)面);3、頁(yè)面加載時(shí)間短(業(yè)務(wù)邏輯復(fù)雜就需要引用第三方的庫(kù),但很可能加載的庫(kù)和用戶本次操作沒(méi)關(guān)系);4,還要代碼好維護(hù)(加入新的邏輯時(shí),影響的文件盡量少)。
想同時(shí)實(shí)現(xiàn)這些目標(biāo),就必須有一套按需加載的機(jī)制,頁(yè)面上展現(xiàn)的內(nèi)容和所有需要依賴的文件,都可以根據(jù)業(yè)務(wù)邏輯需要按需加載。最近都是基于angularjs做開發(fā),所以本文主要圍繞angularjs提供的各種機(jī)制,探索全面實(shí)現(xiàn)按需加載的套路。
一、一步一步實(shí)現(xiàn)基本思路:1、先開發(fā)一個(gè)框架頁(yè)面,它可以完成一些基本的業(yè)務(wù)邏輯,并且支持?jǐn)U展的機(jī)制;2、業(yè)務(wù)邏輯變復(fù)雜,需要把部分邏輯拆分到子頁(yè)面中,子頁(yè)面按需加載;3、子頁(yè)面中的展現(xiàn)內(nèi)容也變了復(fù)雜,又需要進(jìn)行拆分,按需加載;4、子頁(yè)面的內(nèi)容復(fù)雜到依賴外部模塊,需要按需加載angular模塊。
1、框架頁(yè)提到前端的按需加載,就會(huì)想到AMD( Asynchronous Module Definition),現(xiàn)在用requirejs的非常多,所以首先考慮引入requires。
index.html
注意:采用手動(dòng)啟動(dòng)angular的方式,因此html中沒(méi)有ng-app。
spa-loader.js
require.config({ paths: { "domReady": "/static/js/domReady", "angular": "http://cdn.bootcss.com/angular.js/1.4.8/angular.min", "angular-route": "http://cdn.bootcss.com/angular.js/1.4.8/angular-route.min", }, shim: { "angular": { exports: "angular" }, "angular-route": { deps: ["angular"] }, }, deps: ["/test/lazyspa/spa.js"], urlArgs: "bust=" + (new Date()).getTime() });
spa.js
define(["require", "angular", "angular-route"], function(require, angular) { var app = angular.module("app", ["ngRoute"]); require(["domReady!"], function(document) { angular.bootstrap(document, ["app"]); /*手工啟動(dòng)angular*/ window.loading.finish(); }); });2、按需加載子頁(yè)面
angular的routeProvider+ng-view已經(jīng)提供完整的子頁(yè)面加載的方法,直接用。
注意必須設(shè)置html5Mode,否則url變化以后,routeProvider不截獲。
index.html
spa.js
app.config(["$locationProvider", "$routeProvider", function($locationProvider, $routeProvider) { /* 必須設(shè)置生效,否則下面的設(shè)置不生效 */ $locationProvider.html5Mode(true); /* 根據(jù)url的變化加載內(nèi)容 */ $routeProvider.when("/test/lazyspa/page1", { template: "3、按需加載子頁(yè)面中的內(nèi)容page1", }).when("/test/lazyspa/page2", { template: "page2", }).otherwise({ template: "main", }); }]);
用routeProvider的前提是url要發(fā)生變化,但是有的時(shí)候只是子頁(yè)面中的局部要發(fā)生變化。如果這些變化主要是和綁定的數(shù)據(jù)相關(guān),不影響頁(yè)面布局,或者影響很小,那么通過(guò)ng-if一類的標(biāo)簽基本就解決了。但是有的時(shí)候要根據(jù)頁(yè)面狀態(tài),完全改變局部的內(nèi)容,例如:用戶登錄前和登錄后局部要發(fā)生的變化等,這就意味著局部的布局可能也挺復(fù)雜,需要作為獨(dú)立的單元來(lái)對(duì)待。
利用ng-include可以解決頁(yè)面局部?jī)?nèi)容加載的問(wèn)題。但是,我們可以再考慮更復(fù)雜一些的情況。這個(gè)頁(yè)面片段對(duì)應(yīng)的代碼是后端動(dòng)態(tài)生成的,而且不僅僅有html還有js,js中定義了代碼片段對(duì)應(yīng)的controller。這種情況下,不僅僅要考慮動(dòng)態(tài)加載html的問(wèn)題,還要考慮動(dòng)態(tài)定義controller的問(wèn)題。controller是通過(guò)angular的controllerProvider的register方法注冊(cè),因此需要獲得controllerProvider的實(shí)例。
spa.js
app.config(["$locationProvider", "$routeProvider", "$controllerProvider", function($locationProvider, $routeProvider, $controllerProvider) { app.providers = { $controllerProvider: $controllerProvider //注意這里?。?! }; /* 必須設(shè)置生效,否則下面的設(shè)置不生效 */ $locationProvider.html5Mode(true); /* 根據(jù)url的變化加載內(nèi)容 */ $routeProvider.when("/test/lazyspa/page1", { /*!!!頁(yè)面中引入動(dòng)態(tài)內(nèi)容!!!*/ template: "4、動(dòng)態(tài)加載模塊page1", controller: "ctrlPage1" }).when("/test/lazyspa/page2", { template: "page2", }).otherwise({ template: "main", }); app.controller("ctrlPage1", ["$scope", "$templateCache", function($scope, $templateCache) { /* 用這種方式,ng-include配合,根據(jù)業(yè)務(wù)邏輯動(dòng)態(tài)獲取頁(yè)面內(nèi)容 */ /* !!!動(dòng)態(tài)的定義controller!!! */ app.providers.$controllerProvider.register("ctrlPage1Dyna", ["$scope", function($scope) { $scope.openAlert = function() { alert("page1 alert"); }; }]); /* !!!動(dòng)態(tài)定義頁(yè)面的內(nèi)容!!! */ $templateCache.put("page1.html", ""); }]); }]);
采用上面子頁(yè)面片段的加載方式存在一個(gè)局限,就是各種邏輯(js)要加入到啟動(dòng)模塊中,這樣還是限制子頁(yè)面片段的獨(dú)立封裝。特別是,如果子頁(yè)面片段需要使用第三方模塊,且這個(gè)模塊在啟動(dòng)模塊中沒(méi)有事先加載時(shí),就沒(méi)有辦法了。所以,必須要能夠?qū)崿F(xiàn)模塊的動(dòng)態(tài)加載。實(shí)現(xiàn)模塊的動(dòng)態(tài)加載就是把a(bǔ)ngular啟動(dòng)過(guò)程中加載模塊的方式提取出來(lái),再處理一些特殊情況。
動(dòng)態(tài)加載模塊深入分析可以參考這篇文章:
http://www.tuicool.com/articles/jmuymiE
但是,實(shí)際跑起來(lái)發(fā)現(xiàn)文章中的代碼有問(wèn)題,就是“$injector”到底是什么?研究了angular的源代碼injector.js才大概搞明白是怎么回事。
一個(gè)應(yīng)用有兩個(gè)$injector,providerInjector和instanceInjector。invokeQueue和用providerInjector,runBlocks用instanceProvider。如果$injector用錯(cuò)了,就會(huì)找到需要的服務(wù)。
routeProvider中動(dòng)態(tài)加載模塊文件。
template: "", resolve: { load: ["$q", function($q) { var defer = $q.defer(); /* 動(dòng)態(tài)加載angular模塊 */ require(["/test/lazyspa/module1.js"], function(loader) { loader.onload && loader.onload(function() { defer.resolve(); }); }); return defer.promise; }] }page2
動(dòng)態(tài)加載angular模塊
angular._lazyLoadModule = function(moduleName) { var m = angular.module(moduleName); console.log("register module:" + moduleName); /* 應(yīng)用的injector,和config中的injector不是同一個(gè),是instanceInject,返回的是通過(guò)provider.$get創(chuàng)建的實(shí)例 */ var $injector = angular.element(document).injector(); /* 遞歸加載依賴的模塊 */ angular.forEach(m.requires, function(r) { angular._lazyLoadModule(r); }); /* 用provider的injector運(yùn)行模塊的controller,directive等等 */ angular.forEach(m._invokeQueue, function(invokeArgs) { try { var provider = providers.$injector.get(invokeArgs[0]); provider[invokeArgs[1]].apply(provider, invokeArgs[2]); } catch (e) { console.error("load module invokeQueue failed:" + e.message, invokeArgs); } }); /* 用provider的injector運(yùn)行模塊的config */ angular.forEach(m._configBlocks, function(invokeArgs) { try { providers.$injector.invoke.apply(providers.$injector, invokeArgs[2]); } catch (e) { console.error("load module configBlocks failed:" + e.message, invokeArgs); } }); /* 用應(yīng)用的injector運(yùn)行模塊的run */ angular.forEach(m._runBlocks, function(fn) { $injector.invoke(fn); }); };
定義模塊
module1.js
define(["angular"], function(angular) { var onloads = []; var loadCss = function(url) { var link, head; link = document.createElement("link"); link.href = url; link.rel = "stylesheet"; head = document.querySelector("head"); head.appendChild(link); }; loadCss("http://cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css"); /* !!! 動(dòng)態(tài)定義requirejs !!!*/ require.config({ paths: { "ui-bootstrap-tpls": "http://cdn.bootcss.com/angular-ui-bootstrap/1.1.2/ui-bootstrap-tpls.min" }, shim: { "ui-bootstrap-tpls": { deps: ["angular"] } } }); /*!!! 模塊中需要引用第三方的庫(kù),加載模塊依賴的模塊 !!!*/ require(["ui-bootstrap-tpls"], function() { var m1 = angular.module("module1", ["ui.bootstrap"]); m1.config(["$controllerProvider", function($controllerProvider) { console.log("module1 - config begin"); }]); m1.controller("ctrlModule1", ["$scope", "$uibModal", function($scope, $uibModal) { console.log("module1 - ctrl begin"); /*!!! 打開angular ui的對(duì)話框 !!!*/ var dlg = "二、完整的代碼"; dlg += ""; dlg += "I"m a modal!
"; dlg += "content"; dlg += " "; $scope.openDialog = function() { $uibModal.open({ template: dlg, controller: ["$scope", "$uibModalInstance", function($scope, $mi) { $scope.cancel = function() { $mi.dismiss(); }; $scope.ok = function() { $mi.close(); }; }], backdrop: "static" }); }; }]); /* !!!動(dòng)態(tài)加載模塊!!! */ angular._lazyLoadModule("module1"); console.log("module1 loaded"); angular.forEach(onloads, function(onload) { angular.isFunction(onload) && onload(); }); }); return { onload: function(callback) { onloads.push(callback); } }; });
index.html
SPA
spa-loader.js
window.loading = { finish: function() { /* 保留個(gè)方法做一些加載完成后的處理,我實(shí)際的項(xiàng)目中會(huì)在這里結(jié)束加載動(dòng)畫 */ }, load: function() { require.config({ paths: { "domReady": "/static/js/domReady", "angular": "http://cdn.bootcss.com/angular.js/1.4.8/angular.min", "angular-route": "http://cdn.bootcss.com/angular.js/1.4.8/angular-route.min", }, shim: { "angular": { exports: "angular" }, "angular-route": { deps: ["angular"] }, }, deps: ["/test/lazyspa/spa.js"], urlArgs: "bust=" + (new Date()).getTime() }); } }; window.loading.load();
spa.js
"use strict"; define(["require", "angular", "angular-route"], function(require, angular) { var app = angular.module("app", ["ngRoute"]); /* 延遲加載模塊 */ angular._lazyLoadModule = function(moduleName) { var m = angular.module(moduleName); console.log("register module:" + moduleName); /* 應(yīng)用的injector,和config中的injector不是同一個(gè),是instanceInject,返回的是通過(guò)provider.$get創(chuàng)建的實(shí)例 */ var $injector = angular.element(document).injector(); /* 遞歸加載依賴的模塊 */ angular.forEach(m.requires, function(r) { angular._lazyLoadModule(r); }); /* 用provider的injector運(yùn)行模塊的controller,directive等等 */ angular.forEach(m._invokeQueue, function(invokeArgs) { try { var provider = providers.$injector.get(invokeArgs[0]); provider[invokeArgs[1]].apply(provider, invokeArgs[2]); } catch (e) { console.error("load module invokeQueue failed:" + e.message, invokeArgs); } }); /* 用provider的injector運(yùn)行模塊的config */ angular.forEach(m._configBlocks, function(invokeArgs) { try { providers.$injector.invoke.apply(providers.$injector, invokeArgs[2]); } catch (e) { console.error("load module configBlocks failed:" + e.message, invokeArgs); } }); /* 用應(yīng)用的injector運(yùn)行模塊的run */ angular.forEach(m._runBlocks, function(fn) { $injector.invoke(fn); }); }; app.config(["$injector", "$locationProvider", "$routeProvider", "$controllerProvider", function($injector, $locationProvider, $routeProvider, $controllerProvider) { /** * config中的injector和應(yīng)用的injector不是同一個(gè),是providerInjector,獲得的是provider,而不是通過(guò)provider創(chuàng)建的實(shí)例 * 這個(gè)injector通過(guò)angular無(wú)法獲得,所以在執(zhí)行config的時(shí)候把它保存下來(lái) */ app.providers = { $injector: $injector, $controllerProvider: $controllerProvider }; /* 必須設(shè)置生效,否則下面的設(shè)置不生效 */ $locationProvider.html5Mode(true); /* 根據(jù)url的變化加載內(nèi)容 */ $routeProvider.when("/test/lazyspa/page1", { template: "page1", controller: "ctrlPage1" }).when("/test/lazyspa/page2", { template: "", resolve: { load: ["$q", function($q) { var defer = $q.defer(); /* 動(dòng)態(tài)加載angular模塊 */ require(["/test/lazyspa/module1.js"], function(loader) { loader.onload && loader.onload(function() { defer.resolve(); }); }); return defer.promise; }] } }).otherwise({ template: "page2main", }); }]); app.controller("ctrlMain", ["$scope", "$location", function($scope, $location) { console.log("main controller"); /* 根據(jù)業(yè)務(wù)邏輯自動(dòng)到缺省的視圖 */ $location.url("/test/lazyspa/page1"); }]); app.controller("ctrlPage1", ["$scope", "$templateCache", function($scope, $templateCache) { /* 用這種方式,ng-include配合,根據(jù)業(yè)務(wù)邏輯動(dòng)態(tài)獲取頁(yè)面內(nèi)容 */ /* 動(dòng)態(tài)的定義controller */ app.providers.$controllerProvider.register("ctrlPage1Dyna", ["$scope", function($scope) { $scope.openAlert = function() { alert("page1 alert"); }; }]); /* 動(dòng)態(tài)定義頁(yè)面內(nèi)容 */ $templateCache.put("page1.html", ""); }]); require(["domReady!"], function(document) { angular.bootstrap(document, ["app"]); }); });
module1.js
"use strict"; define(["angular"], function(angular) { var onloads = []; var loadCss = function(url) { var link, head; link = document.createElement("link"); link.href = url; link.rel = "stylesheet"; head = document.querySelector("head"); head.appendChild(link); }; loadCss("http://cdn.bootcss.com/bootstrap/3.3.6/css/bootstrap.min.css"); require.config({ paths: { "ui-bootstrap-tpls": "http://cdn.bootcss.com/angular-ui-bootstrap/1.1.2/ui-bootstrap-tpls.min" }, shim: { "ui-bootstrap-tpls": { deps: ["angular"] } } }); require(["ui-bootstrap-tpls"], function() { var m1 = angular.module("module1", ["ui.bootstrap"]); m1.config(["$controllerProvider", function($controllerProvider) { console.log("module1 - config begin"); }]); m1.controller("ctrlModule1", ["$scope", "$uibModal", function($scope, $uibModal) { console.log("module1 - ctrl begin"); var dlg = "寫后感"; dlg += ""; dlg += "I"m a modal!
"; dlg += "content"; dlg += " "; $scope.openDialog = function() { $uibModal.open({ template: dlg, controller: ["$scope", "$uibModalInstance", function($scope, $mi) { $scope.cancel = function() { $mi.dismiss(); }; $scope.ok = function() { $mi.close(); }; }], backdrop: "static" }); }; }]); angular._lazyLoadModule("module1"); console.log("module1 loaded"); angular.forEach(onloads, function(onload) { angular.isFunction(onload) && onload(); }); }); return { onload: function(callback) { onloads.push(callback); } }; });
年初定下的目標(biāo)是堅(jiān)持每周寫一篇自己在開發(fā)過(guò)程碰到的問(wèn)題總結(jié),本以為是個(gè)簡(jiǎn)單的事情,寫起來(lái)才發(fā)現(xiàn)寫文章的時(shí)間比寫代碼的花的時(shí)間還要長(zhǎng)。因?yàn)閷懘a的時(shí)候只要功能實(shí)現(xiàn)了就行了,但是,寫文章的時(shí)候就一定要把代碼搞清楚才敢寫,實(shí)際上就是逼著自己要認(rèn)真研究源代碼,雖然壓力很大,但收獲更大。另一方面,發(fā)現(xiàn)找到一個(gè)好題目挺難的,只是簡(jiǎn)單的貼別人的代碼沒(méi)意思,可是自己想出來(lái)有價(jià)值,有意思的問(wèn)題挺難的。因此大家要是覺(jué)得有啥有意思,有價(jià)值前端問(wèn)題,分享一下吧,給我的年度寫作計(jì)劃幫幫忙
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/78737.html
摘要:現(xiàn)在微軟終于痛定思痛決定放棄了不支持的安全更新,對(duì)我們前端來(lái)說(shuō),真的是重大利好啊言歸正傳,這篇文章的目的就是把怎么用構(gòu)建一個(gè)單頁(yè)面程序介紹以下,是對(duì)自己的一個(gè)總結(jié),也喜歡對(duì)大家有一定的借鑒作用,寫的不好不對(duì)的地方希望大家多評(píng)論評(píng)論謝謝。 這篇文章是寫在公司項(xiàng)目結(jié)束之后的,因?yàn)槲覀€(gè)人不太會(huì)把沒(méi)有實(shí)踐過(guò)的東西寫出來(lái),實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn)么,用的怎么樣,好不好用,在成熟實(shí)踐過(guò)的項(xiàng)目上能體...
摘要:最近一段時(shí)間在學(xué)習(xí),由于覺(jué)得直接使用它需要加載很多的文件,因此想使用來(lái)實(shí)現(xiàn)異步加載,并動(dòng)態(tài)注入控制器。手動(dòng)啟動(dòng),特別說(shuō)明此處的不是那個(gè)框架,而是的一個(gè)手動(dòng)啟動(dòng)框架的函數(shù)中完成了各模塊的初始化,并且引入了。 最近一段時(shí)間在學(xué)習(xí)angularjs,由于覺(jué)得直接使用它需要加載很多的js文件,因此想使用requirejs來(lái)實(shí)現(xiàn)異步加載,并動(dòng)態(tài)注入控制器。簡(jiǎn)單搜索了下發(fā)現(xiàn)好多教程寫的都很復(fù)雜,所...
摘要:的成功離開不這三個(gè)東西,分層架構(gòu),路由系統(tǒng),儲(chǔ)存系統(tǒng)。分層架構(gòu)是我們組織復(fù)雜代碼的關(guān)鍵,路由系統(tǒng)是將多個(gè)頁(yè)面壓縮在一個(gè)頁(yè)面的關(guān)鍵。在這個(gè)種子工程中,我都調(diào)用了同一個(gè)方法,就比較適合目錄動(dòng)態(tài)生成,需要按需調(diào)用不同的頁(yè)面的情況。 SPA的成功離開不這三個(gè)東西,分層架構(gòu),路由系統(tǒng),儲(chǔ)存系統(tǒng)。分層架構(gòu)是我們組織復(fù)雜代碼的關(guān)鍵,路由系統(tǒng)是將多個(gè)頁(yè)面壓縮在一個(gè)頁(yè)面的關(guān)鍵。 其中avalon路由用到...
摘要:特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒(méi)想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請(qǐng)斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...
摘要:目前已經(jīng)在大大小小多個(gè)線上產(chǎn)品中使用了,也收集了一些有效的建議好了,該看下一個(gè)最簡(jiǎn)單的組件長(zhǎng)什么樣吧免費(fèi)領(lǐng)取驗(yàn)證碼內(nèi)容安全短信發(fā)送直播點(diǎn)播體驗(yàn)包及云服務(wù)器等套餐更多網(wǎng)易技術(shù)產(chǎn)品運(yùn)營(yíng)經(jīng)驗(yàn)分享請(qǐng)?jiān)L問(wèn)網(wǎng)易云社區(qū)。文章來(lái)源網(wǎng)易云社區(qū) 本文由作者鄭海波授權(quán)網(wǎng)易云社區(qū)發(fā)布。 此文摘自regularjs的指南, 目前指南正在全面更新, 把老文檔的【接口/語(yǔ)法部分】統(tǒng)一放到了獨(dú)立的 Reference...
閱讀 669·2023-04-25 15:49
閱讀 3121·2021-09-22 15:13
閱讀 1262·2021-09-07 10:13
閱讀 3484·2019-08-29 18:34
閱讀 2567·2019-08-29 15:22
閱讀 513·2019-08-27 10:52
閱讀 691·2019-08-26 18:27
閱讀 3028·2019-08-26 13:44