AngularJS框架研究(一)


和幾年前相比,現在的Javascript開發方式有了翻天覆地的變化,一些極具創意的開發工具的出現,讓前端開發更加成熟,Angular框架就是一個例子。最近在看Angular的源代碼,發現並不是件容易的事。JS開發已經並不單純局限在敲JS代碼, 從代碼的編寫,到集成單元測試,再到發布,每個環節都已經專業化,很多新鮮的開發工具都需要去了解。
 
比如和CSS相關的 SCSS,將編碼引入到CSS中(作者肯定是一個超級懶惰的程序員),使得CSS的開發和維護效率以幾何級增長;又比如時下火熱的 Node.js,把JavaScript移植到服務器端,可以編寫Web應用、游戲服務器等等。實際Node.js並不限於服務器端開發,還可以用來編寫本地腳本,實現自動化部署腳本。看看現在開源的JavaScript庫,基本上都引入了基於Node.js的 Grunt來實現腳本的自動化發布。
 
像我這樣,接觸Javascript已經很多年了,但去看Angular源碼時,還是有種無處下手的尷尬。下載Angular的源碼,一進去就葷菜了,一大堆的文件。經過一番探索后,總算有了點頭緒。Angular是一個頗龐大的框架,發布版本(單個js文件)有1萬五前多行代碼(包含注釋),維護這樣一個文件顯然不是人力所能及也,所以和很多JS庫一樣,在開發時將文件按照功能拆分為多個子文件,這樣便於維護。發布時,使用Grunt腳本來組合以及壓縮。
 
package.json和Gruntfile.js是Grunt的兩個配置文件,定義了各種發布任務。AngularJS使用了 bower來管理依賴的JS庫,在bower.json中定義了相關信息,依賴的庫有jQuery, bootstrap, closure等,不過這些庫都只是在跑單元測試時才用到。有一堆以karma前綴開頭的文件,這是單元測試工具 karma相關的東東。如果只是看源碼,可以不用理會這些非js文件,直接看src目錄即可。
 
在src目錄下,有三個文件:Angular.js, angular.prefix, angular.suffix。從文件名可以看出,這是程序主體。prefix是文件頭部,suffix是文件尾。suffix的代碼如下:
  //try to bind to jquery now so that one can write angular.element().read()
  //but we will rebind on bootstrap again.
  bindJQuery();

  publishExternalAPI(angular);

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

})(window, document);

紅色部分標出的正是AngularJS的入口。AngularJS內置了jQuery的輕量版本jqLite,具體代碼見src/jqLite.js。bindJquery函數會嘗試去綁定jQuery庫,如果沒有找到,就用內置的jqLite。DOM加載完畢后,執行angularInit函數,作准備工作。

function angularInit(element, bootstrap) {
  var elements = [element],
      appElement,
      module,
      names = ['ng:app', 'ng-app', 'x-ng-app', 'data-ng-app'],
      NG_APP_CLASS_REGEXP = /\sng[:\-]app(:\s*([\w\d_]+);?)?\s/;

  function append(element) {
    element && elements.push(element);
  }

  forEach(names, function(name) {
    names[name] = true;
    append(document.getElementById(name));
    name = name.replace(':', '\\:');
    if (element.querySelectorAll) {
      forEach(element.querySelectorAll('.' + name), append);
      forEach(element.querySelectorAll('.' + name + '\\:'), append);
      forEach(element.querySelectorAll('[' + name + ']'), append);
    }
  });

  forEach(elements, function(element) {
    if (!appElement) {
      var className = ' ' + element.className + ' ';
      var match = NG_APP_CLASS_REGEXP.exec(className);
      if (match) {
        appElement = element;
        module = (match[2] || '').replace(/\s+/g, ',');
      } else {
        forEach(element.attributes, function(attr) {
          if (!appElement && names[attr.name]) {
            appElement = element;
            module = attr.value;
          }
        });
      }
    }
  });
  if (appElement) {
    bootstrap(appElement, module ? [module] : []);
  }
}

angularInit函數主要用來尋找主程序入口。如果在DOM中找到了ng-app標記,則調用bootstrap開始初始化框架。如果沒有定義app標記,則需要手動調用angular.bootstrap來初始化。app標記一般在html節點,也可以放置在任意的節點上,app節點所在的DOM樹都會被AngularJS框架遍歷解析。

ng-app屬性如果有值,即自定義module,也會被解析出來,前提是我們必須先創建module,用來管理全局的injector行為和對象。如果沒有值,則會創建默認的module。

function bootstrap(element, modules) {
  var doBootstrap = function() {
    element = jqLite(element);

    if (element.injector()) {
      var tag = (element[0] === document) ? 'document' : startingTag(element);
      throw ngMinErr('btstrpd', "App Already Bootstrapped with this Element '{0}'", tag);
    }

    modules = modules || [];
    modules.unshift(['$provide', function($provide) {
      $provide.value('$rootElement', element);
    }]);
    modules.unshift('ng');
    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);
        });
        animate.enabled(true);
      }]
    );
    return injector;
  };

  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步驟分為三步:首先加載module,然后創建全局injector對象,最后執行compile動作,對應的正是上面的代碼。

AngularJS會為每個應用程序創建唯一的injector對象,它可以看作是一個對象池,依靠鍵值來存取實例,比如存放數據的model,和后端交互的service等,而實例的創建則由module來決定。

在AngularJS框架中,應用程序被看作是由多個module組成的一個結合體,而一個module,往往是相似功能塊的組合。在一個大型應用程序中,我們習慣將程序切分為多個模塊並行開發,這也是AngularJS的推薦做法。在module中,可以定義和View層打交道的Controller,和后台交互的Service,也可以自定義依賴注入行為,解析特殊的DOM數據。

從上面的代碼可以看到,初始化時會加載內置的模塊,比如“ng”。在ng模塊中定義了AngularJS的核心功能,包括解析DOM樹中的以“ng”為前綴的自定義節點, 比如ng-model,ng-class,ng-repeat等等。

compile,編譯,顧名思義,就是將DOM中的ng標記和其他自定義標記解析為真正的View, Model和Controller等。

--------------------

到這里為止,對AngularJS的運行機制還處於一知半解,不得不吐槽下,想要弄懂AngularJS的運行機制實在不是件容易的事。

最后再補充一點:有一款名為"Batarang"的Chrome擴展插件一定不能錯過,AngularJS開發調試必備。用過后,才知道官網上一些截圖是怎么來的了~


免責聲明!

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



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