天天看點

我的angularjs源碼學習之旅1——初識angularjs

  angular誕生有好幾年光景了,有Google公司的支援版本更新還是比較快,從一開始就是一個熱門技術,但是本人近期才開始接觸到。隻能感慨自己學習起點有點晚了。隻能是加倍努力趕上技術前線。

  因為有分析jQuery源碼學到很多東西的原因,是以本人對新技術還是抱有追根問底的習慣,希望能從本質上了解他們。前兩天剛剛完成nodejs編寫的一個小網站,給俺媳婦用的,是以就沒有挂到外網上,隻能本機啟動自己用。開發完成後有點小收獲小感悟就在這裡唠叨幾句。

  第一個要唠叨的是關于抛異常。對前端來說,前端抛異常很多時候是不用去處理使用者也感覺不出來的;而後端一抛異常如果沒有異常處理機制,那就是整個程式直接挂掉了。從這個上面來說本人感覺後端的代碼必須穩定、健壯,是以給個人的感覺是後端程式員更加嚴肅,而前端程式員更加的活潑,當然這裡并不是說前端出異常就不去處理。

  第二個是加密的問題。對于前端來說加密往往是後端的事,往往傳輸給後端的都是明文密碼,本人對此也很難以了解,按說前端也應該加密才對,至少哪些個截獲我們發送的資訊的人需要一定代價才能破解我們的明文密碼。但實際運用中往往都沒有前端加密這個環節,盡管大部分網站都聲稱,不會存儲使用者的明文密碼。但這并沒有證據,也許私下裡仍在悄悄儲存。如果在前端加密,網站就無法拿到使用者的明文密碼了。也許正是這一點,很多網站不願意使用前端加密。現在用nodejs了,那麼前端人去做後端程式也應當對密碼加密才對。關于加密可以參考這篇文章對抗拖庫 —— Web 前端慢加密

  第三個是和資料庫打交道。對于簡單的系統來說還好,至少不會花太多的時間去學習資料庫查詢,但是如果是比較大的系統的話,那麼需要花更多的時間去學習和優化資料庫查詢了,這是一個很讓前端人頭疼的事。

  第四是關于模闆引擎的事。作為前端人員來說最不希望的是html代碼中插入一些業務代碼。比如nodejs比較推薦的ejs妥妥的jsp風格。本人是不贊成這種寫法的,給後端開發人員用還可以。html就應該是純html,沒有任何業務邏輯,特别是下面這種情況:html代碼和邏輯代碼完全雜在一起了。

<% if (names.length) { %>  
        <ul>  
          <% names.forEach(function(name){ %>  
            <li><%= name %></li>  
          <% }) %>  
        </ul>  
      <% } %>      

  對比angular:有邏輯,但是邏輯隻是标簽的屬性而已,給人的感覺與純html一樣,看着舒服很多。這也是前端人員比較能接受的方式。

<ul>
    <li ng-repeat="x in names">{{x}} {{lastname}}
</ul>      

  好了,唠叨了半天。學習nodejs也就到一段落,畢竟本人也沒想真的做全棧式工程師,專攻前端是本人的理想。

  先前通過菜鳥教程學習了angular的基本知識(本人英語不太好,要是看英語教程那叫一個頭大)。本人有幾個好奇。

  1.MVC/MVP/MVVM這三個東東到底是什麼東西?本人一直都是一知半解

  2.如下的代碼,input是怎麼和{{name}}關聯的?架構是怎麼儲存Hello {{name}}的,必須要儲存吧,不然我改變了input内容架構怎麼知道去重新整理h1。

<div ng-app="">
     <p>名字 : <input type="text" ng-model="name"></p>
     <h1>Hello {{name}}</h1>
</div>      

  3.如下的代碼中,函數中怎麼知道我是依賴的$scope,怎麼實作的依賴注入?

<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
    $scope.firstName = "John";
    $scope.lastName = "Doe";
});
</script>      

  最後就羅列出了一堆的名稱:MVVM、自動化雙向資料綁定、依賴注入、髒檢測等等。

  

  分析一個源碼最先要跟蹤的就是他的執行流程,這是第一步。我們的執行個體代碼是

<div ng-app="myApp" ng-controller='myCtrl'>
      <input type="text" ng-model='name'/>
      <span style='width: 100px;height: 20px;    margin-left: 300px;'>{{name}}</span>
  </div>
  <script>
  var app = angular.module('myApp', []);
  app.controller('myCtrl', function($scope) {
      $scope.name = 1;
  });
  </script>      

  本人跟蹤angular執行流程如下。

1.bindJQuery();嘗試綁定jQuery,如果沒有jQuery則使用内置的JQLite

2.publishExternalAPI(angular);初始化angular的各種外部api。可以看一下初始化之前的angular對象是

  

我的angularjs源碼學習之旅1——初識angularjs

  初始化完成以後是

angular = {
  $$csp: function(),
  $$minErr: minErr(module, ErrorConstructor),
  $interpolateMinErr: function(),
  bind: bind(self, fn),
  bootstrap: bootstrap(element, modules, config),
  callbacks: Object,
  copy: copy(source, destination, stackSource, stackDest),
  element: JQLite(element),
  equals: equals(o1, o2),
  extend: extend(dst),
  forEach: forEach(obj, iterator, context),
  fromJson: fromJson(json),
  getTestability: getTestability(rootElement),
  identity: identity($),
  injector: createInjector(modulesToLoad, strictDi),
  isArray: isArray(),
  isDate: isDate(value),
  isDefined: isDefined(value),
  isElement: isElement(node),
  isFunction: isFunction(value),
  isNumber: isNumber(value),
  isObject: isObject(value),
  isString: isString(value),
  isUndefined: isUndefined(value),
  lowercase: (string),
  merge: merge(dst),
  module: module(name, requires, configFn),
  noop: noop(),
  reloadWithDebugInfo: reloadWithDebugInfo(),
  toJson: toJson(obj, pretty),
  uppercase: (string),
  version: Object,
}      

  可以看到給angular添加了很多方法和屬性。

  其中用到 angularModule = setupModuleLoader(window);是用來給angular上添加module方法(angular添加子產品的函數)

  

我的angularjs源碼學習之旅1——初識angularjs

  這個module方法有一個外部變量var modules = {};這個變量的作用馬上就能看到。  

  angularModule('ng', ['ngLocale'], ['$provide',function ngModule($provide) {...}]);

  執行結果會得到以後将會得到(這裡面這個modules即是angular.module函數的那個外部變量)

modules.ng = moduleInstance = {
  _invokeQueue: [], 
  _configBlocks:[["$injector","invoke",["$provide",ngModule($provide)]]], 
  _runBlocks: [], 
  animation: funciton(recipeName, factoryFunction),
  config: function(),
  constant: function(),
  controller: function(recipeName, factoryFunction),
  decorator: function(recipeName, factoryFunction),
  directive: function(recipeName, factoryFunction),
  factory: function(recipeName, factoryFunction),
  filter: function(recipeName, factoryFunction),
  name: "ng",
  provider: function(recipeName, factoryFunction),
  requires: ["ngLocale"],
  run: function(block),
  service: function(recipeName, factoryFunction),
  value: function()
}      

  拆解一下這個函數的内部執行步驟和結果:

  1)先定義了三個數組invokeQueue = [];var configBlocks = [];var runBlocks = [];

  顧名思義invokeQueue是執行隊列;configBlocks是配置塊,馬上我們就會對這個配置塊指派; runBlocks是運作了的塊。

  2)執行var config = invokeLater('$injector', 'invoke', 'push', configBlocks);得到的config如下

  

我的angularjs源碼學習之旅1——初識angularjs

  3)對象moduleInstance初始化,初始化中主要調用了兩個方法invokeLater和invokeLaterAndSetModuleName,結果為

  

我的angularjs源碼學習之旅1——初識angularjs

  其中config屬性對應的函數就是第二步的config。

  可以看到執行裡面的函數大都是在往invokeQueue隊列裡面塞執行資料,每一個執行資料包括三個元素:provider/method/arguments。後面正真執行的時候調用方式是provider[method].apply(provider, arguments)。

  run函數把block放入到runBlock中。裡面有個requires屬性,表示要依賴的子產品,比如目前name為“ng”時requires為["ngLocale"]。

  小點:moduleInstance的大多數方法屬性最後又傳回了moduleInstance對象,和jQuery類似,這樣實作鍊式調用。

  4)執行if (configFn) {config(configFn);}

  

我的angularjs源碼學習之旅1——初識angularjs

  結合第二步的config函數來看即把('$injector','invoke', ['$provide',function ngModule($provide) {...}])塞入到configBlocks中

  

我的angularjs源碼學習之旅1——初識angularjs

  5)傳回處理後的moduleInstance對象,這個對象就是modules.ng

3.調用angular.module("ngLocale", [], ["$provide", function($provide) {...}])再次添加一個ngLocale子產品。

  先前modules隻有一個ng子產品,現在變成了兩個子產品。

  

我的angularjs源碼學習之旅1——初識angularjs

4.最後是等待文檔加載完成以後進行angular初始化,這裡面的初始化主要是識别html中的指令、

jqLite(document).ready(function() {
    angularInit(document, bootstrap);
  });      

  angularInit中處理是比較簡單的,查找符合格式的"ng-"/"data-ng-"/"ng:"/"x-ng-" + "app"标簽,作為使用angular的的标志。平常我們都使用ng-app,

ng-app 指令用于告訴 AngularJS 應用目前這個元素是根元素。所有 AngularJS 應用都必須要要一個根元素。HTML 文檔中隻允許有一個 

ng-app

 指令,如果有多個 

ng-app

 指令,則隻有第一個會被使用。

  找到根元素,代入bootstrap中執行

//config中包含依賴注入是否是嚴格注入的标志;module是子產品名稱,也就是ng-app指定的名稱,appElement是angular應用的根元素的DOM對象
bootstrap(appElement, module ? [module] : [], config);      

  比較重要的是bootstrap中調用

var doBootstrap = function() {
    element = jqLite(element);

    if (element.injector()) {
      var tag = (element[0] === document) ? 'document' : startingTag(element);
      //Encode angle brackets to prevent input from being sanitized to empty string #8683
      throw ngMinErr(
          'btstrpd',
          "App Already Bootstrapped with this Element '{0}'",
          tag.replace(/</,'&lt;').replace(/>/,'&gt;'));
    }

    modules = modules || [];
    modules.unshift(['$provide', function($provide) {
      $provide.value('$rootElement', element);
    }]);

    if (config.debugInfoEnabled) {
      // Pushing so that this overrides `debugInfoEnabled` setting defined in user's `modules`.
      modules.push(['$compileProvider', function($compileProvider) {
        $compileProvider.debugInfoEnabled(true);
      }]);
    }

    modules.unshift('ng');
    var injector = createInjector(modules, config.strictDi);
    injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
       function bootstrapApply(scope, element, compile, injector) {
        scope.$apply(function() {
          element.data('$injector', injector);
          compile(element)(scope);
        });
      }]
    );
    return injector;
  };      

  在初始化注入函數createInjector之前,modules結構如下

  

我的angularjs源碼學習之旅1——初識angularjs

  正真最重要的函數:createInjector(初始化依賴注入)。createInjector需要單獨拿出來說

 5.createInjector  

function createInjector(modulesToLoad, strictDi) {
  strictDi = (strictDi === true);
  var INSTANTIATING = {},
      providerSuffix = 'Provider',
      path = [],
      loadedModules = new HashMap([], true),
      providerCache = {
        $provide: {
            provider: supportObject(provider),
            factory: supportObject(factory),
            service: supportObject(service),
            value: supportObject(value),
            constant: supportObject(constant),
            decorator: decorator
          }
      },
      providerInjector = (providerCache.$injector =
          createInternalInjector(providerCache, function(serviceName, caller) {
            if (angular.isString(caller)) {
              path.push(caller);
            }
            throw $injectorMinErr('unpr', "Unknown provider: {0}", path.join(' <- '));
          })),
      instanceCache = {},
      instanceInjector = (instanceCache.$injector =
          createInternalInjector(instanceCache, function(serviceName, caller) {
            var provider = providerInjector.get(serviceName + providerSuffix, caller);
            return instanceInjector.invoke(provider.$get, provider, undefined, serviceName);
          }));


  forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });

  return instanceInjector;
  ...      

  傳回的是instanceInjector。裡面重要的幾個變量的關系是providerInjector = providerCache.$injector;instanceInjector = instanceCache.$injector。

  而providerCache的結構是

  

我的angularjs源碼學習之旅1——初識angularjs

  instanceCache的結構是

    

我的angularjs源碼學習之旅1——初識angularjs

  我們的執行個體代碼最終走到createInjector函數中的

forEach(loadModules(modulesToLoad), function(fn) { if (fn) instanceInjector.invoke(fn); });      

  此時modulesToLoad為 ["ng", [ "$provide",function ($provide){...}], "myApp"]。

function loadModules(modulesToLoad) {
    assertArg(isUndefined(modulesToLoad) || isArray(modulesToLoad), 'modulesToLoad', 'not an array');
    var runBlocks = [], moduleFn;
    forEach(modulesToLoad, function(module) {
//loadedModules = new HashMap([], true),這是一個哈希存儲結構,将modulesToLoad裡面的元素都存到hash表中
      if (loadedModules.get(module)) return;
      loadedModules.put(module, true);

      function runInvokeQueue(queue) {
        var i, ii;
        for (i = 0, ii = queue.length; i < ii; i++) {
          var invokeArgs = queue[i],
              provider = providerInjector.get(invokeArgs[0]);
          //這裡便是之前說的provider[method].apply(provider, arguments)的調用
          provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
        }
      }

      try {
        if (isString(module)) {//當module為字元串
          //angularModule即angular.module,調用後傳回moduleInstance對象
          moduleFn = angularModule(module);
          //把所有依賴子產品的runBlocks都取出
          runBlocks = runBlocks.concat(loadModules(moduleFn.requires)).concat(moduleFn._runBlocks);
 //将執行西面的兩個隊列
          runInvokeQueue(moduleFn._invokeQueue);
          runInvokeQueue(moduleFn._configBlocks);
        } else if (isFunction(module)) {
            runBlocks.push(providerInjector.invoke(module));
        } else if (isArray(module)) {
//第二參數 [ "$provide",function ($provide){...}],invoke方法執行後将結果儲存存到runBlocks
            runBlocks.push(providerInjector.invoke(module));
        } else {
          assertArgFn(module, 'module');
        }
      } catch (e) {
        ...
      }
    });
    return runBlocks;
  }      

  還有一個比較重要的函數createInternalInjector,顧名思義即用來建立依賴注入的。下一章接着分析angular實作的依賴注入。

  建立了依賴注入對象injector,接下來就馬上用起來了

6.執行injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',       function bootstrapApply(scope, element, compile, injector) {...}])。

  這個函數執行實作了資料的髒檢測,使資料雙向綁定。後面會分析他的實作方式。

  好了,通過這6步,頁面初始化即完成。裡面有很多細節無法分析到位,本人覺得也沒必要細究,畢竟還不是angular的技術創新點,大體了解一些angular執行流程即可,後面的分析才會分析angular的技術點。因為本人也是邊看邊跟蹤流程,有不對的地方望大牛指出。

  

  如果覺得本文不錯,請點選右下方【推薦】!

轉載于:https://www.cnblogs.com/chuaWeb/p/angular-process.html

繼續閱讀