1. 程式人生 > >angular 1.0源代碼分析

angular 1.0源代碼分析

.data sta attr 代碼 self fun 代碼執行 註入 root

本文分析angular 1.0從初始化開始到編譯網頁更新頁面的源代碼過程以及一些重要細節。

測試項目例子:

<html ng-app=‘myapp‘ >
<body ng-controller="myController" >
<tip title="title"></tip>
</body>

controller和指令用angular.module的方法定義。
angular 1.0 以指令為中心,directive指令標簽就是組件,有template,而屬性指令主要是修改元素屬性,angular 2.0改為以組件為中心設計。


angular入口初始化程序:
function angularInit(element, bootstrap) {
  bootstrap(appElement, module ? [module] : []);
}

function bootstrap(element, modules) {

  var doBootstrap = function() {

    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‘, //angular代碼執行時已經創建內部對象,這些是對象的key(名字)
    function(scope, element, compile, injector, animate) { // invoke([模塊1,模塊2,...,fn])就是調用執行fn,傳遞依賴模塊(angular內部對象)
      scope.$apply(function() { // 外套$apply執行方法,執行完方法之後掃描watcher重新獲取所有watcher表達式的值進行必要的頁面更新
      element.data(‘$injector‘, injector);
      compile(element)(scope); // 編譯根元素,返回link函數,再執行link函數,更新頁面的代碼在每個指令表達式watcher的update方法中。
      });
    }]);
  return injector;
  };
  return doBootstrap();

}

function createInternalInjector(cache, factory) {
  function invoke(fn, self, locals){ //invoke就是變換參數和作用域調用函數, angular內部對象機制和依賴模塊註入非常復雜,本文忽略
    fn = fn[length];
    return fn.apply(self, args);


function compile($compileNodes, transcludeFn, maxPrior //從根元素開始編譯
  var compositeLinkFn = compileNodes($compileNodes, transcludeFn, $compileNodes, // 遞歸編譯子節點
    function compileNodes(nodeList, transcludeFn, $rootElement,

      applyDirectivesToNode(directives,

        function applyDirectivesToNode(directives, compileNode, templateAttrs, transcludeFn,
          $compileNode.html(directiveValue); // 把指令的template<div>{{title}}</div>插入網頁中的節點元素中

      childLinkFn = compileNodes(childNodes,nodeLinkFn ? nodeLinkFn.transclude : transcludeFn); // 有子節點則遞歸調用自身
      return linkFnFound ? compositeLinkFn : null;
//每一次遞歸子節點時已經編譯了子節點的指令,遞歸子節點層層返回到最上層compile代碼位置時,返回的link函數已經包含
每一層遞歸時產生的link函數,也就是每一層遞歸時編譯結果。最後只要再執行最終返回的link函數,傳遞根scope,把根scope保存
在根元素對象屬性中,就完成了整個編譯插入網頁的過程,在每一次遞歸編譯子節點時如果有指令會編譯指令。

如果是路由組件,有template,是編譯插入網頁生效。

function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) { //每一次遞歸返回的link函數,含每一次遞歸編譯的結果數據
  return function publicLinkFn(scope, cloneConnectFn, // compile返回的link函數,返回後會執行link函數傳遞scope
    $linkNode.eq(i).data(‘$scope‘, scope); // node是元素jquery對象,把scope保存到元素對象的屬性中
      if (cloneConnectFn) cloneConnectFn($linkNode, scope);
      if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode); // link函數含層層遞歸編譯結果數據
      return $linkNode;

angular的link函數比較費解,它主要是關聯元素和scope/controller instance,一般情況下是沒用的,scope從rootscope開始就是一個樹結構,從rootscope可以遞歸所有的子scope,

controller的方法中引用某一個已經創建存在的scope,scope按id區分和索引,都和元素沒關系,數據層面與元素是沒有直接關系的。

編譯方法compile外套$apply,$apply會調用$digest:
  $digest: function() {
    //遞歸scope找watcher,watcher數據中沒有scope,因此執行watcher的方法時要傳遞scope,執行watcher的方法時會變換作用域為scope
    value = watch.get(current) // 獲取表達式的值時要傳遞scope,表達式{{title}}的值是hello
    watch.fn(value, ((last === initWatchVal) ? value : last), current); // 執行watch.fn之後網頁顯示hello,fn就是handler/update函數

代碼中還涉及到defer和settimeout是延遲函數,實現異步調度,非功能性流程。


因此angular在初始化時編譯網頁時針對每個表達式建立了watcher,編譯程序外套$apply,會調用$digest掃描執行watcher的update方法更新網頁。


vue也是在初始化編譯template時針對每個表達式建立watcher,為組件的data屬性建立set/get方法,只要set數據操作,就會執行相應的watcher的update更新頁面。
vue 2.0是針對組件建立watcher,初始化時編譯根組件執行根組件watcher的update更新頁面,set數據時執行相應的組件的watcher的update方法更新頁面,組件
可能有子組件嵌套,那麽從組件遞歸重新編譯template產生vnode,再根據vnode更新頁面。

angular是在數據操作之後執行$digest掃描執行watcher更新頁面,vue是set觸發執行watcher更新頁面。

react是在初始化編譯時遞歸編譯子節點,然後把編譯結果插入網頁生效,當數據變化時,通過Connect組件的Listener或setState執行組件的render()方法重新編譯組件
更新頁面。

angular和vue都是用compile方法遞歸編譯網頁元素,這是它們的核心程序,react也是類似的,其mountComponent其實就是遞歸編譯程序,因此所有的框架在基本原理方法方面其實都是用類似的設計方法,都是設計一個核心編譯程序,遞歸編譯所有的子節點。

在template和表達式的寫法方面不太一樣,react比較特殊,它用render()方法寫template,用JSX語法,用babel編譯解析,產生一個含層層嵌套的createElement()的函數,執行這個函數就產生一個根元素。

vue 2.0也是用類似的方法,編譯template產生一個render方法代碼,含層層嵌套的createElement方法,再執行render方法代碼產生一個根元素Element。

angular 1.0源代碼分析