angular問題總結與反思


      因為工作中實際開發需要,才開始接觸angular框架。從當初的比葫蘆畫瓢,被各種問題、概念折磨摧殘,到現在有一定的了解認識,覺得有必要將自己的認識進行簡單的總結。不到位的地方還望多多包涵。

 

1.雙向數據綁定

      目前業內盛行各種MV**框架,相關的框架不斷涌現,而angular就是其中的一種(MVVM)。MV**框架其實最核心的問題就是將view層和model分離開來,降低代碼的耦合性,做到數據和表現的分離,MVC、MVP、MVVM均有相同的目標,而他們之間的不同就在於如何把model層和view關聯起來。

      數據在model、view層如何流動就成了問題的關鍵,angular通過dirty-check實現了數據的雙向綁定。所謂的雙向綁定,就是view的變化可以反映到model層,而model數據的變化可以在view體現出來。那么angular是如何做到雙向綁定的?為何成為dirty-check呢?還是前端的一個原始問題出發吧:

 1 html:
 2 <input type="button" value="increase 1" id="J-increase" />
 3 <span id="J-count"></span>
 4  
 5 js:
 6 <script>
 7     var bindDate = {
 8         count: 1,
 9         appy: function () {
10             document.querySelector('#J-count').innerHTML = this.count;
11         },
12         increase: function () {
13             var _this = this;
14             document.querySelector('#J-increase').addEventListener('click', function () {
15                 _this.count++;
16                 appy();
17             }, true);
18         },
19         initialize: function () {
20             // 初始化
21             this.appy();
22             //
23             this.increase();
24         }
25     };
26     bindDate.initialize();
27 </script>

     在上面的例子中,存在兩個過程:

  • view層影響model層: 頁面上點擊button,造成數據count的數量增加1
  • model層反應view層: count發生完變化以后,通過apply函數來反映到view層上

      這是以前使用jquery、YUI等類庫實現的數據處理,這里面存在的問題很明顯: 1. 涉及到了大量的DOM操作; 2. 過程繁瑣;3.代碼耦合性太高,不便於寫單元測試。下面來看看angular是如何進行數據處理的:

      第一步. 添加watcher:就是當數據發生變化的時候,需要檢測哪些對象,需要先進行注冊

 1 // 對angular里面的源碼進行了精簡  
 2 $watch: function(watchExp, listener, objectEquality) {
 3     var scope = this,
 4         array = scope.$$watchers,
 5         watcher = {
 6             fn: listener,
 7             last: initWatchVal,
 8             get: get,
 9             exp: watchExp,
10             eq: !!objectEquality
11         };
12     if (!array) {
13         array = scope.$$watchers = [];
14     }
15     array.unshift(watcher);
16 }

      第二步. dirty-check:就是當有某個scope作用域下的數據發生變化后,需要遍歷檢測注冊的$$watchers = [...]

1 $digest: function() {
2     while (length--) {
3         watch = watchers[length];
4         watch.fn(value, lastValue, scope);
5     }
6 }

      這樣就實現了數據的雙向綁定,上面的實現是否跟自定義事件很像呢?可以看到使用了觀察者設計模式或(publisher-subscriber)。

 

2.依賴注入

     使用過spring框架的同學都知道,Ioc、AOP是spring里面最重要的兩個概念,而Ioc就可以里面為注入依賴(DI),很明顯angular帶有非常濃厚的后端色彩。

     同樣,首先來看下不使用DI,是如何解決對象相互依賴的:

 1 function Car() {
 2     ...
 3 }
 4 Car.prototype = {
 5     run: function () {...}
 6 }
 7  
 8 function Benz() {
 9     var cat = new Car();
10 }
11 Benz.prototype = {
12     ...
13 }

      在上面的例子中,類Benz依賴於類Car,直接通過內部New來解決這種依賴關系。這樣做的弊端非常明顯,代碼耦合性變高,不利於維護。后端框架很早就意識到了這個問題,spring早期通過在xml文件中注冊對象之間的依賴關系,后來有通過anotation的方式更加方便地解決DI問題,COS端的同學可以看看后端的代碼。

     js語言本身是不具有注解(annotation)機制的,那angular是如何實現的呢?

    1.模擬注解 

 1 // 注解的模擬
 2 function annotate(fn, strictDi, name) {
 3     var $inject;
 4     if (!($inject = fn.$inject)) {
 5         $inject = [];
 6         $inject.push(name);
 7     }else if (isArray(fn)) {
 8         $inject = fn.slice(0, last);
 9     }
10     return $inject;
11 }
12 createInjector.$$annotate = annotate;

     2. 注入對象的創建

 1 function createInjector(modulesToLoad, strictDi) {
 2     //通過singleton模式創建對象
 3     var providerCache = {
 4         $provide: {
 5             provider: supportObject(provider),
 6             factory: supportObject(factory),
 7             service: supportObject(service),
 8             value: supportObject(value),
 9             constant: supportObject(constant),
10             decorator: decorator
11           }
12       },
13     instanceCache = {},
14     instanceInjector = (instanceCache.$injector =
15         createInternalInjector(instanceCache, function(serviceName, caller) {
16             var provider = providerInjector.get(serviceName + providerSuffix, caller);
17             return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
18         }));
19     return instanceInjector;
20 }

     3. 獲取注入對象

 1 function invoke(fn, self, locals, serviceName) {
 2     var args = [],
 3         $inject = annotate(fn, strictDi, serviceName);
 4  
 5     for (...) {
 6         key = $inject[i];
 7         // 替換成依賴的對象
 8         args.push(
 9           locals && locals.hasOwnProperty(key)
10           ? locals[key]
11           : getService(key, serviceName)
12         );
13     }
14     if (isArray(fn)) {
15         fn = fn[length];
16     }      
17     return fn.apply(self, args);
18 }

       到這里,是否是看到很多后端框架設計的思路,沒有anotation就模擬一個,難怪PPK要說angular是" a front-end framework by non-front-enders for non-front-enders"

 

3.controller通信

    在實際開發中,應用系統會非常龐大,一個應用app不可能只存在一個controller,那么不同controller之間就存在通信的可能,如何解決這個常見問題,主要有兩種方法:

    1.事件機制: 把事件注冊在$rootScope上,這樣做的問題就是會在$rootScope上注冊太大事件,會引起一些列后續問題

 1 //controller1
 2 app.controller('controller1', function ($rootScope) {
 3     $rootScope.$on('eventType', function (arg) {
 4         ......
 5     })
 6 })
 7  
 8 // controller2
 9 app.controller('controller2', function ($rootScope) {
10     $rootScope.$emit('eventType',arg);
11     or
12     $rootScope.$broadcast('eventType',arg);
13 })

      2.通過service: 充分利用angular的DI特性,利用service是單例的特點,在不同controller之間起到橋梁作用

 1 // 注冊service
 2 app.service('Message', function () {
 3     return {
 4         count: void(0);
 5     }
 6 })
 7  
 8 // controller1,修改service的count值
 9 app.controller('controller1', function ($scope, Message) {
10     $scope.count = 1;
11     Message.count = $scope.count;
12 });
13  
14 // controller2, 獲取service的count值
15 app.controller('controller2', function ($scope, Message) {
16     $scope.num = Message.count;
17 });

 

4.service的特點

     1. 單例(singleton): angular里面只有service才可以進行DI諸如,controller、directive這些均不具有這些功能,service字面上就是提供一些基本的服務,跟具體的業務沒有關聯,而controller、directive則與具體業務緊密相關聯,所以需要保證service的唯一性。

     2. lazy new:angular首先會生成service的provider,但是並沒有立即生成對應的service,只有到需要這些服務的時候才會進行實例化操作。

     3. provider)的分類: provider()、factory、service、value、constant,其中provider是最底層的實現,其他方式都是在其基礎上的語法糖(sugar),需要注意的是這些服務最終均要添加$get方法,因為具體service是通過執行$get方法生成的。

 

5. directive的實現

     directive的編譯(compiler)包括兩個階段: compile、link。簡單來講compile階段主要處理template DOM,此時並不涉及作用域問題,也就是沒有進行數據渲染,例如ngRepeate指令就是通過compile進行template修改的,執行compile后會返回link函數,覆蓋后面定義的link函數;而link主要是進行數據渲染,分為pre-link和post-link兩個環節,這兩個環節解析的順序是相反,post-link是先解析內部,然后才是外部,這樣對directive的解析就是安全的,因為directive內部還可以包括directive,同時link是對真正DOM的處理,會涉及DOM操作的性能問題。

 

參考

https://angularjs.org/

https://github.com/xufei/blog/issues/10

http://teropa.info/blog/2013/11/03/make-your-own-angular-part-1-scopes-and-digest.html

http://www.quirksmode.org/blog/archives/2015/01/the_problem_wit.html

http://www.jvandemo.com/the-nitty-gritty-of-compile-and-link-functions-inside-angularjs-directives/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM