九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
AngularJS按需動態(tài)加載template和controller?
初次使用angularjs做項目,但是發(fā)現(xiàn)angularjs在路由配置后會一次性加載所有依賴文件,這對于大一點的項目來說是不可接受的,使用requirejs也不能阻止路由配置處angularjs自己去加載文件。

然后我找到了angularAMD,在angularjs和angular-ui-router的環(huán)境下是可以實現(xiàn)template、controller的動態(tài)加載(也就是進入哪個頁面就加載改頁面相關(guān)內(nèi)容)。
但是由于不想自己折騰太多的UI,我使用了很火的框架ionic,他對路由模塊可能坐了自己的封裝,使用了<ion-nav-view>而不是<ui-view>,然后導(dǎo)致控制器能按需加載,模板卻一次性加載。

我谷歌了這些內(nèi)容,發(fā)現(xiàn)相關(guān)話題非常少,難道是我理解有誤?angular不需要動態(tài)加載模塊嗎,但是對于有很多個頁面的項目,這肯定是不合理的,譬如我們要做的是hybird app,頁面量很大。不知道大家在使用angular時都是怎么對待這個需求的
按投票排序按時間排序

10 個回答

占位 謝@張治中邀請

樓上的幾個答案我都看了一下

1、把單頁面應(yīng)用做成應(yīng)用中所有頁面都加載并且重新初始化Angularjs框架的行為在我個人的角度是無法接受的,不論是從交互體驗的角度上還是從技術(shù)追求的角度。當(dāng)然,如果從公司的角度,加上其他因素的影響,最后根據(jù)各方面實際情況(開發(fā)周期、團隊技術(shù)儲備、產(chǎn)品經(jīng)理交互需求等等)的妥協(xié)與折衷,這樣做其實也是可以的。

2、使用oclazyload的方案我調(diào)研了一下,是可行的,但是想說這個方案有些缺點,比如每次動態(tài)加載需要的腳本、模版資源會有很多不必要的網(wǎng)絡(luò)開銷,路由定義比較復(fù)雜(多了一些配置項,其實不能算復(fù)雜,而是繁瑣),對于大型復(fù)雜業(yè)務(wù)應(yīng)用,路由眾多,耗費的精力不可忽視。在實際做對外開放的產(chǎn)品時,我們一般會把使用requirejs管理依賴關(guān)系的代碼打包壓縮,加版本號,同時根據(jù)項目情況決定要不要按照業(yè)務(wù)模塊做拆分打包&異步按需加載。

3、不知道樓主開發(fā)的產(chǎn)品是不是移動端的單頁面應(yīng)用,不知道樓主的應(yīng)用是否業(yè)務(wù)復(fù)雜以及腳本文件的大小在什么量級,所以我下面講解的技術(shù)方案可能在某些地方并不適合樓主的場景,但是原理是相通的,樓主可以參考一下然后看看是否可以解決你的項目中面臨的問題。如果需要,可以隨時找我一起討論你所面臨的問題以及采用哪種方式解決最好。

4、占位,晚上有空更新。

===========2015-06-08更新============

最近一直比較忙,今天抽空更新一部分。

看了樓主的評論回復(fù),移動端和PC瀏覽器端,對于Angular本身而言沒有區(qū)別,所以我說的這些應(yīng)該也適用于移動端Angularjs應(yīng)用。

首先說一下Angularjs的啟動原理,就知道為什么很少有人做Angular代碼的異步加載了。

好像知乎不支持markdown格式,寫起來好難受,只能拿 “等號”玩了

========================Angular框架啟動原理分析==============================

我們現(xiàn)在一般配合Requirejs 做代碼依賴管理的情況下,都是在RequireJs的入口文件中,執(zhí)行以下代碼來啟動Angular框架:

angular.element(document).ready(function() {    angular.bootstrap(document, ['vpcConsole']);  });
這種情況下,我們來扒代碼看看 angular.bootstrap都干了什么(無關(guān)緊要的代碼用省略號表示了):
function bootstrap(element, modules) {  var doBootstrap = function() {        //獲取Jquery的Dom對象    element = jqLite(element);     ……    //這句話是最關(guān)鍵的點,我們會繼續(xù)深挖createInjector這個函數(shù)    var injector = createInjector(modules);    injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector', '$animate',       function(scope, element, compile, injector, animate) {        scope.$apply(function() {          element.data('$injector', injector);          compile(element)(scope);        });      }]    );    return injector;  };   //以下代碼應(yīng)該是為了支持測試用的,不用關(guān)心,感興趣可以看文檔說明:http://docs.angularjs.cn/guide/bootstrap  var NG_DEFER_BOOTSTRAP = /^NG_DEFER_BOOTSTRAP!/;  if (window && !NG_DEFER_BOOTSTRAP.test(window.name)) {    return doBootstrap();  }  window.name = window.name.replace(NG_DEFER_BOOTSTRAP, '');  angular.resumeBootstrap = function(extraModules) {    forEach(extraModules, function(module) {      modules.push(module);    });    doBootstrap();  };}

在上面的bootstrap方法里面,我們找到了一行非常關(guān)鍵的代碼,調(diào)用了createInjector方法,接下來我們看下createInjector方法里面干了什么事情(不重要的代碼統(tǒng)統(tǒng)省略號)。
function createInjector(modulesToLoad) {  /*    開始部分申明了一堆變量,無視掉   */  ……//重要的代碼,loadModules方法需要深挖  forEach(loadModules(modulesToLoad), function(fn) { instanceInjector.invoke(fn || noop); });  return instanceInjector;/*源碼里面這里寫了一堆函數(shù)定義,由于function 聲明會在js解析引擎里面被提前,所以前面有return語句也沒關(guān)系*/……}


然后再來看 loadModules里面的關(guān)鍵語句(loadModules函數(shù)其實就定義在了 createInjector函數(shù)里面)
function loadModules(modulesToLoad){    var runBlocks = [], moduleFn, invokeQueue, i, ii;    forEach(modulesToLoad, function(module) {      if (loadedModules.get(module)) return;      loadedModules.put(module, true);      try {        if (isString(module)) {          //得到angular模塊          moduleFn = angularModule(module);          runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);          //這里使用for循環(huán)讀取了modelFn的_invokeQueue,然后做遍歷執(zhí)行,這個_invokeQueue 是很關(guān)鍵的東西,等你知道了它的由來,就知道為什么Angularjs天然不支持異步加載了。          for(invokeQueue = moduleFn._invokeQueue, i = 0, ii = invokeQueue.length; i < ii; i++) {            var invokeArgs = invokeQueue[i],                provider = providerInjector.get(invokeArgs[0]);            provider[invokeArgs[1]].apply(provider, invokeArgs[2]);          }        } else if (isFunction(module)) {            runBlocks.push(providerInjector.invoke(module));        } else if (isArray(module)) {            runBlocks.push(providerInjector.invoke(module));        } else {          assertArgFn(module, 'module');        }      } catch (e) {      /*       異常處理,不關(guān)心      */              ……      }    });    return runBlocks;  }
上面的代碼中已經(jīng)發(fā)現(xiàn)_invokeQueue 是個很重要的東西,那么我們來看下它在什么地方生成的(這個地方代碼比較長,但是非常非常關(guān)鍵,無用代碼直接省略號)。
function setupModuleLoader(window) {   ……  function ensure(obj, name, factory) {    return obj[name] || (obj[name] = factory());  }    //給window對象添加一個 angular 屬性  var angular = ensure(window, 'angular', Object);  ……  return ensure(angular, 'module', function() {       var modules = {};    return function module(name, requires, configFn) {      ……      return ensure(modules, name, function() {               //這個就是我們上面提到的_invokeQueue;        var invokeQueue = [];        /** @type {!Array.<Function>} */        var runBlocks = [];        var config = invokeLater('$injector', 'invoke');        //這個就是我們調(diào)用 angular.module()以后得到的實例,注意它對外暴露的接口都是用什么實現(xiàn)的(invokeLater做的實現(xiàn))        var moduleInstance = {          //這個就是很重要的module的一個屬性,_invokeQueue,前面的代碼很清楚,它是一個數(shù)組,而且這個數(shù)組里面的東西,只有在angular.bootstrap的時候才會被執(zhí)行,明白這一點非常關(guān)鍵非常重要。          _invokeQueue: invokeQueue,          _runBlocks: runBlocks,          requires: requires,          name: name,          /*            下面這些就是我們經(jīng)常調(diào)用的各種接口,注意它的實現(xiàn),都是使用invokeLater來做了一次封裝,下面的代碼注釋里面說明了invokeLater的作用。          */          provider: invokeLater('$provide', 'provider'),          factory: invokeLater('$provide', 'factory'),          service: invokeLater('$provide', 'service'),          value: invokeLater('$provide', 'value'),          constant: invokeLater('$provide', 'constant', 'unshift'),          animation: invokeLater('$animateProvider', 'register'),          filter: invokeLater('$filterProvider', 'register'),          controller: invokeLater('$controllerProvider', 'register'),          directive: invokeLater('$compileProvider', 'directive'),          config: config,          run: function(block) {            runBlocks.push(block);            return this;          }        };        if (configFn) {          config(configFn);        }        return  moduleInstance;         /*這里是InvokeLater的實現(xiàn),我們調(diào)用 Angular的module實例所注冊的factory、filter、controller、directive 等等都是通過這個方法放在了 invokeQueue里面,也就是說,當(dāng)我們在代碼里面執(zhí)行 xxxModule.directive('mydirective',['xxx',function(xxx){}])的時候,其實這個directive并沒有真正被注冊,而是放在了一個invokeQueue里面,知道這一點很重要。同理,我們定義一個controller的時候,也沒有真正注冊執(zhí)行這個controller,而是這個controller被放在了一個數(shù)組里面,等著angular.bootstrap真正去執(zhí)行并實例化這個函數(shù)         */        function invokeLater(provider, method, insertMethod) {          return function() {            invokeQueue[insertMethod || 'push']([provider, method, arguments]);            return moduleInstance;          };        }      });    };  });}

代碼扒到這里就差不多了。如果你看明白了整個angular.bootstrap的時候的來龍去脈,就會發(fā)現(xiàn),假設(shè)我當(dāng)前頁面已經(jīng)加載了相關(guān)資源,Angular框架已經(jīng)運行起來(執(zhí)行了angualr.bootstrap方法),那么我后續(xù)通過按需加載引入的javascript腳本文件中的那些
xxxModule.controller("xxx",['yyy',function(yyy){}])、
xxxModule.directive("zzz",['www',function(www){}])

代碼都雖然會在requirejs引入的時候被執(zhí)行一遍,但是執(zhí)行的結(jié)果僅僅是把這些controller和directive和factory等等函數(shù)放在了一個invokeLater的數(shù)組里面,我們的前端路由激活的時候,去通過angular尋找對應(yīng)路由(視圖)的controller的時候,發(fā)現(xiàn)根本沒有這個controller,原因就是在這里:Angular框架啟動以后(執(zhí)行了bootstrap方法之后),它讀取controller構(gòu)造函數(shù)、directive構(gòu)造函數(shù)等的地方和我們執(zhí)行 controller方法、directive方法所注冊進去的代碼不是一個地方(或者說不是緩存在一個變量里面),所以,在按需加載的情況下,雖然我們的代碼看起來執(zhí)行了,但是真正Angular的部分卻并沒有真正的執(zhí)行,而是僅僅被放在一個地方等待著“invokeLater”(其實再等多久也沒用,因為不能再執(zhí)行angular.bootstrap方法了)。

=======================異步按需加載方案分析======================

AngularJs框架啟動原理分析完以后,就可以分析異步按需加載方案了。首先我們來看一下我們做異步按需加載方案需要解決哪些問題:
1、angularjs框架啟動后,調(diào)用 各種api無法真正注冊相關(guān)構(gòu)造函數(shù)的問題
2、路由激活時,加載當(dāng)前路由需要的腳本資源(考慮防止重復(fù)加載、考慮最大化利用客戶端緩存等等問題)
3、當(dāng)前單頁面應(yīng)用模塊劃分和打包(當(dāng)上面兩個技術(shù)問題解決以后,就要考慮這個偏向業(yè)務(wù)的問題了)

我們先來看第一個問題怎么解決:
1、解決異步按需加載代碼后Angular原生代碼無法真正注冊各種構(gòu)造函數(shù)的問題

經(jīng)過上面的加載原理分析以后,我們就知道該怎么辦了:把angular.module("xxx")的實例的factory、controller、directive、value、filter 等等方法都“變換”掉,讓我們的代碼執(zhí)行這些方法的時候,直接把我們的函數(shù)放在運行期的對應(yīng)的緩存的對象里面,這樣一來異步加載的代碼就會在執(zhí)行的時候真正被注冊到Angular運行時可以讀取的地方(Angular運行時具體緩存各個構(gòu)造函數(shù)的地方自己扒源碼吧,懶得貼出來了),這樣在路由激活的時候,就可以找到對應(yīng)的controller,然后執(zhí)行。

這個辦法可以參考下面的代碼(可以參考前面同學(xué)回答的oclazyload這個框架里面的代碼,但是這個框架作了很多其他的事情,導(dǎo)致最核心的思想沒有很清晰的體現(xiàn),不過也可以讀一讀,挺有意思):
(function(){    var app = angular.module('app', []);     app.config(function($routeProvider, $controllerProvider, $compileProvider, $filterProvider, $provide)    {        app.controllerProvider = $controllerProvider;        app.compileProvider    = $compileProvider;        app.routeProvider      = $routeProvider;        app.filterProvider     = $filterProvider;        app.provide            = $provide;         // Register routes with the $routeProvider    });})();

解決這個問題以后,我們來看第2個按路由加載代碼的問題如何解決:(精力有限,暫時更新到這里,預(yù)計本周還會更新一次,先給個提示:按照路由去加載所需的代碼方案有很多種,常見的是就在定義路由的時候定義一個 resolve,在里面做資源的加載,但是這種方法需要在路由定義的敵方寫比較多的東西,不是很喜歡這種方式。題主你的手機端應(yīng)用建議你做好合理的模塊或者說頁面的劃分,我初步的建議是你每個頁面內(nèi)部的資源全部都打包成一個腳本包括視圖模板也打包進去,最終看文件的大小是不是在移動端單次web請求允許的范圍之內(nèi),然后angularjs相關(guān)的和公共代碼打包成一個,應(yīng)用啟動時加載這個公共的腳本,切換到其他頁面或者路由的時候公共的資源腳本已經(jīng)運行起來,不需要再加載,而只需加載對應(yīng)頁面或者路由的腳本就可以了。初步可以這樣,后續(xù)再詳細(xì)更新說明具體的方案和原因。)


===========2015-06-21更新============
抱歉最近非常忙,更新時間略長


之前的內(nèi)容講解了Angularjs支持動態(tài)加載腳本相關(guān)的局限性(動態(tài)引入到瀏覽器里面的代碼中,沒有經(jīng)過處理的話,所有的 xxxModule.directive()、xxxModule.controller() 執(zhí)行過后并沒有真正立刻被實例化,而是放在了一個緩存中等待app bootstrap的時候去執(zhí)行,可是由于這些代碼是動態(tài)引入的,在它們引入之前,app 已經(jīng)bootstrap過了。),現(xiàn)在開始講如何配合 requirejs 做相關(guān)的處理來支持動態(tài)加載:

先看下有幾個問題需要解決:

1、確定按需加載的靜態(tài)資源引入頁面的時機。
2、按需加載的靜態(tài)資源打包方式。
3、其他一些問題的整體考慮。

依次回答上面三個問題,加上之前更新的內(nèi)容,然后關(guān)于AngularJs動態(tài)加載腳本的問題和相關(guān)的原理基本上可以說明白了。

1: 毫無疑問,在用戶瀏覽某個“頁面”的時候,應(yīng)用會激活對應(yīng)的路由,然后去加載對應(yīng)的資源腳本。在我們實際開發(fā)過程中,一個業(yè)務(wù)模塊下面會有n個相關(guān)的子頁面來共同服務(wù)于某個業(yè)務(wù)需求,這種情況下,我們會給這n個子頁面定義一個最基本的父路由,在進入該父路由的時候,去判斷當(dāng)前路由相關(guān)的資源腳本是否加載,如果沒有加載的話,則利用requirejs去 獲取資源腳本文件,等拿到并執(zhí)行以后,再執(zhí)行相關(guān)的業(yè)務(wù)邏輯(比如激活某個頁面的路由、實例化對應(yīng)的controller函數(shù));如果已經(jīng)加載過了,則和正常的路由解析過程一致,直接依次執(zhí)行其子路由直到用戶想要訪問的頁面對應(yīng)的路由激活。所以說,在不引入其他第三方庫的情況下(或者已經(jīng)引入了某個庫,不再適合引入其他庫的情況下),我們可能需要在路由定義的地方做手腳,加入自己的靜態(tài)資源加載服務(wù)。

2:由于每個業(yè)務(wù)模塊有n個頁面,所以一定有大量的其他代碼一起配合完成相關(guān)的業(yè)務(wù)邏輯,比如有很多directive,有很多filter,這些腳本有的是我們單頁面應(yīng)用里面其他業(yè)務(wù)模塊用不到的,有的是其他業(yè)務(wù)模塊可以共用的,所以我們在開發(fā)過程中,除了以業(yè)務(wù)模塊為維度統(tǒng)一劃分開發(fā)文件結(jié)構(gòu)以外,還會多出一個 common 或者 base 這樣類型的文件夾,專門負(fù)責(zé)放置應(yīng)用內(nèi)部各個業(yè)務(wù)模塊都使用的公共代碼或者公共組件等等。這種情況下,我們的項目中,app文件夾下面的一級文件夾就是 common + n 個業(yè)務(wù)模塊文件夾。在這種項目文件結(jié)構(gòu)下,我們會把 common 和所有用到的第三方組件資源腳本打包、壓縮在一個文件中,在用戶打開瀏覽器加載整個單頁面應(yīng)用的時候最先加載并執(zhí)行,其他n個業(yè)務(wù)模塊,每一個文件夾下的腳本資源單獨打包壓縮成一個js文件,每個業(yè)務(wù)模塊文件夾下需要一個種子文件去引用依賴當(dāng)前項目文件夾中的其他資源腳本,這樣在使用r.js做打包的時候,就可以以這個文件為種子文件將其依賴的腳本打包到一個文件里面。在打包業(yè)務(wù)模塊的腳本的時候,一定會有某些腳本引用了 common 里的文件或者第三方組件中的文件,所以需要在打包配置項中,做一些配置,告訴打包工具不把聲明的這些文件打包到當(dāng)前文件中。具體的配置項可以參考 r.js 中的 path配置項,其中某個子項如果使用 xxx:“empty:” 這樣的寫法的話,則該聲明意為不把 xxx 打包到當(dāng)前打包的文件中??梢詤⒖紃.js的示例配置文件:github.com/jrburke/r.js

這樣,打包部分的方式和方法也說明了,具體相關(guān)配置不太熟悉的同學(xué)可以看requirejs的官方文檔或者直接看 r.js 的github項目r.js/example.build.js at master · jrburke/r.js · GitHub

大部分同學(xué)應(yīng)該是使用 grunt-requirejs做打包的,其實和r.js是一樣的,所以看上面的鏈接地址的代碼和注釋就可以了。

3:在做這樣的技術(shù)改造或者技術(shù)升級的時候,需要考慮很多很多因素,而不是僅僅去找?guī)讉€相同的例子然后把自己的代碼改成那樣就好了,而是需要考慮已有代碼做升級的時候,如何才能改動最小,對已有代碼侵入最少,還要考慮到對開發(fā)人員的友好性,考慮到測試和線上調(diào)試的方便程度,還要考慮到開發(fā)團隊內(nèi)部其他成員的接受程度等等。以題主目前的處境來看,其實你的業(yè)務(wù)代碼的按需加載已經(jīng)由你采用的框架幫你實現(xiàn),現(xiàn)在你的困惑主要集中在模板部分不能按需加載,所以,我們可以把目光直接聚焦到如何解決 模板不能按需加載的問題上,而不用再去考慮如何做業(yè)務(wù)腳本的按需加載的整體實現(xiàn)。所以,針對題主你的問題,我建議如下(假設(shè)你的HTML模板都已經(jīng)打包到了一個文件中):1、使用 grunt-html2js 打包轉(zhuǎn)換HTML模板為js文件 2、打包后的模板緩存文件和業(yè)務(wù)模塊代碼打包到一起 3、業(yè)務(wù)腳本動態(tài)引入的時候,自動引入HTML模板的js文件,這樣就實現(xiàn)了HTML模板的動態(tài)引入。原理:Angularjs 的指令、State 等等在處理 templateUrl 的時候,會去服務(wù)端按照templateUrl的地址發(fā)起請求,但是在請求真正發(fā)起之前會使用這個Url作為key去 $templateCache 中查找有沒有該key對應(yīng)的緩存,如果有的話,則不會真的發(fā)起請求,而是直接采用緩存中的HTML字符串。所以我們的 grunt-html2js會生成js文件,這個文件里面,每個HTML模板都會以某個Url(這個Url取決于自己的配置)作為自己的key,然后把模板內(nèi)容轉(zhuǎn)義為字符串,存入$templateCache中。所以,題主,你可以按照這個思路重新調(diào)整一下模板相關(guān)的部分,多做debug,觀察每個視圖激活的過程,看下templateUrl的處理,就知道該怎么做了。




http://www.zhihu.com/question/30624377
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
如何在 ASP
AngularJs的UI組件ui-Bootstrap分享(一)
AngularJS學(xué)習(xí)筆記2——AngularJS的初始化
?。?!對比Angular/jQueryUI/Extjs:沒有一個框架是萬能的
AngularJs學(xué)習(xí)筆記--bootstrap
20個為前端開發(fā)者準(zhǔn)備的文檔和指南
更多類似文章 >>
生活服務(wù)
熱點新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服