成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Angular源碼分析之$compile

xiaoxiaozi / 971人閱讀

摘要:上述代碼采用了依賴注入的方式注入了五個服務,分別用于實現(xiàn)依賴注入的注入器,代碼解析器,控制器服務根作用域服務和指令解析服務。緊接著,執(zhí)行函數(shù),執(zhí)行指令相關操作,并返回處理后的鏈接函數(shù)。

@(Angular)

$compile,在Angular中即“編譯”服務,它涉及到Angular應用的“編譯”和“鏈接”兩個階段,根據(jù)從DOM樹遍歷Angular的根節(jié)點(ng-app)和已構造完畢的 $rootScope對象,依次解析根節(jié)點后代,根據(jù)多種條件查找指令,并完成每個指令相關的操作(如指令的作用域,控制器綁定以及transclude等),最終返回每個指令的鏈接函數(shù),并將所有指令的鏈接函數(shù)合成為一個處理后的鏈接函數(shù),返回給Angluar的bootstrap模塊,最終啟動整個應用程序。

[TOC]

Angular的compileProvider

拋開Angular的MVVM實現(xiàn)方式不談,Angular給前端帶來了一個軟件工程的理念-依賴注入DI。依賴注入從來只是后端領域的實現(xiàn)機制,尤其是javaEE的spring框架。采用依賴注入的好處就是無需開發(fā)者手動創(chuàng)建一個對象,這減少了開發(fā)者相關的維護操作,讓開發(fā)者無需關注業(yè)務邏輯相關的對象操作。那么在前端領域呢,采用依賴注入有什么與之前的開發(fā)不一樣的體驗呢?

我認為,前端領域的依賴注入,則大大減少了命名空間的使用,如著名的YUI框架的命名空間引用方式,在極端情況下對象的引用可能會非常長。而采用注入的方式,則消耗的僅僅是一個局部變量,好處自然可見。而且開發(fā)者僅僅需要相關的“服務”對象的名稱,而不需要知道該服務的具體引用方式,這樣開發(fā)者就完全集中在了對象的接口引用上,專注于業(yè)務邏輯的開發(fā),避免了反復的查找相關的文檔。

前面廢話一大堆,主要還是為后面的介紹做鋪墊。在Angular中,依賴注入對象的方式依賴與該對象的Provider,正如小結標題的compileProvider一樣,該對象提供了compile服務,可通過injector.invoke(compileProvider.$get,compileProvider)函數(shù)完成compile服務的獲取。因此,問題轉移到分析compileProvider.$get的具體實現(xiàn)上。

compileProvider.$get
this.$get = ["$injector", "$parse", "$controller", "$rootScope", "$http", "$interpolate",
      function($injector, $parse, $controller, $rootScope, $http, $interpolate) {
  ...
  return compile;
}

上述代碼采用了依賴注入的方式注入了$injector,$parse,$controller,$rootScope,$http,$interpolate五個服務,分別用于實現(xiàn)“依賴注入的注入器($injector),js代碼解析器($parse),控制器服務($controller),根作用域($rootScope),http服務和指令解析服務”。compileProvider通過這幾個服務單例,完成了從抽象語法樹的解析到DOM樹構建,作用域綁定并最終返回合成的鏈接函數(shù),實現(xiàn)了Angular應用的開啟。

$get方法最終返回compile函數(shù),compile函數(shù)就是$compile服務的具體實現(xiàn)。下面我們深入compile函數(shù):

function compile($compileNodes, maxPriority) {
      var compositeLinkFn = compileNodes($compileNodes, maxPriority);

      return function publicLinkFn(scope, cloneAttachFn, options) {
        options = options || {};
        var parentBoundTranscludeFn = options.parentBoundTranscludeFn;
        var transcludeControllers = options.transcludeControllers;
        if (parentBoundTranscludeFn && parentBoundTranscludeFn.$$boundTransclude) {
          parentBoundTranscludeFn = parentBoundTranscludeFn.$$boundTransclude;
        }
        var $linkNodes;
        if (cloneAttachFn) {
          $linkNodes = $compileNodes.clone();
          cloneAttachFn($linkNodes, scope);
        } else {
          $linkNodes = $compileNodes;
        }
        _.forEach(transcludeControllers, function(controller, name) {
          $linkNodes.data("$" + name + "Controller", controller.instance);
        });
        $linkNodes.data("$scope", scope);
        compositeLinkFn(scope, $linkNodes, parentBoundTranscludeFn);
        return $linkNodes;
      };
    }

首先,通過compileNodes函數(shù),針對所需要遍歷的根節(jié)點開始,完成指令的解析,并生成合成之后的鏈接函數(shù),返回一個publicLinkFn函數(shù),該函數(shù)完成根節(jié)點與根作用域的綁定,并在根節(jié)點緩存指令的控制器實例,最終執(zhí)行合成鏈接函數(shù)。

合成鏈接函數(shù)的生成

通過上一小結,可以看出$compile服務的核心在于compileNodes函數(shù)的執(zhí)行及其返回的合成鏈接函數(shù)的執(zhí)行。下面,我們深入到compileNodes的具體邏輯中去:

function compileNodes($compileNodes, maxPriority) {
      var linkFns = [];
      _.times($compileNodes.length, function(i) {
        var attrs = new Attributes($($compileNodes[i]));
        var directives = collectDirectives($compileNodes[i], attrs, maxPriority);
        var nodeLinkFn;
        if (directives.length) {
          nodeLinkFn = applyDirectivesToNode(directives, $compileNodes[i], attrs);
        }
        var childLinkFn;
        if ((!nodeLinkFn || !nodeLinkFn.terminal) &&
            $compileNodes[i].childNodes && $compileNodes[i].childNodes.length) {
          childLinkFn = compileNodes($compileNodes[i].childNodes);
        }
        if (nodeLinkFn && nodeLinkFn.scope) {
          attrs.$$element.addClass("ng-scope");
        }
        if (nodeLinkFn || childLinkFn) {
          linkFns.push({
            nodeLinkFn: nodeLinkFn,
            childLinkFn: childLinkFn,
            idx: i
          });
        }
      });

      // 執(zhí)行指令的鏈接函數(shù)
      function compositeLinkFn(scope, linkNodes, parentBoundTranscludeFn) {
        var stableNodeList = [];
        _.forEach(linkFns, function(linkFn) {
          var nodeIdx = linkFn.idx;
          stableNodeList[linkFn.idx] = linkNodes[linkFn.idx];
        });

        _.forEach(linkFns, function(linkFn) {
          var node = stableNodeList[linkFn.idx];
          if (linkFn.nodeLinkFn) {
            var childScope;
            if (linkFn.nodeLinkFn.scope) {
              childScope = scope.$new();
              $(node).data("$scope", childScope);
            } else {
              childScope = scope;
            }

            var boundTranscludeFn;
            if (linkFn.nodeLinkFn.transcludeOnThisElement) {
              boundTranscludeFn = function(transcludedScope, cloneAttachFn, transcludeControllers, containingScope) {
                if (!transcludedScope) {
                  transcludedScope = scope.$new(false, containingScope);
                }
                var didTransclude = linkFn.nodeLinkFn.transclude(transcludedScope, cloneAttachFn, {
                  transcludeControllers: transcludeControllers,
                  parentBoundTranscludeFn: parentBoundTranscludeFn
                });
                if (didTransclude.length === 0 && parentBoundTranscludeFn) {
                  didTransclude = parentBoundTranscludeFn(transcludedScope, cloneAttachFn);
                }
                return didTransclude;
              };
            } else if (parentBoundTranscludeFn) {
              boundTranscludeFn = parentBoundTranscludeFn;
            }

            linkFn.nodeLinkFn(
              linkFn.childLinkFn,
              childScope,
              node,
              boundTranscludeFn
            );
          } else {
            linkFn.childLinkFn(
              scope,
              node.childNodes,
              parentBoundTranscludeFn
            );
          }
        });
      }

      return compositeLinkFn;
    }

代碼有些長,我們一點一點分析。
首先,linkFns數(shù)組用于存儲每個DOM節(jié)點上所有指令的處理后的鏈接函數(shù)和子節(jié)點上所有指令的處理后的鏈接函數(shù),具體使用遞歸的方式實現(xiàn)。隨后,在返回的compositeLinkFn中,則是遍歷linkFns,針對每個鏈接函數(shù),創(chuàng)建起對應的作用域對象(針對創(chuàng)建隔離作用域的指令,創(chuàng)建隔離作用域對象,并保存在節(jié)點的緩存中),并處理指令是否設置了transclude屬性,生成相關的transclude處理函數(shù),最終執(zhí)行鏈接函數(shù);如果當前指令并沒有鏈接函數(shù),則調用其子元素的鏈接函數(shù),完成當前元素的處理。

在具體的實現(xiàn)中,通過collectDirectives函數(shù)完成所有節(jié)點的指令掃描。它會根據(jù)節(jié)點的類型(元素節(jié)點,注釋節(jié)點和文本節(jié)點)分別按特定規(guī)則處理,對于元素節(jié)點,默認存儲當前元素的標簽名為一個指令,同時掃描元素的屬性和CSS class名,判斷是否滿足指令定義。

緊接著,執(zhí)行applyDirectivesToNode函數(shù),執(zhí)行指令相關操作,并返回處理后的鏈接函數(shù)。由此可見,applyDirectivesToNode則是$compile服務的核心,重中之重!

applyDirectivesToNode函數(shù)

applyDirectivesToNode函數(shù)過于復雜,因此只通過簡單代碼說明問題。
上文也提到,在該函數(shù)中執(zhí)行用戶定義指令的相關操作。

首先則是初始化相關屬性,通過遍歷節(jié)點的所有指令,針對每個指令,依次判斷$$start屬性,優(yōu)先級,隔離作用域,控制器,transclude屬性判斷并編譯其模板,構建元素的DOM結構,最終執(zhí)行用戶定義的compile函數(shù),將生成的鏈接函數(shù)添加到preLinkFns和postLinkFns數(shù)組中,最終根據(jù)指令的terminal屬性判斷是否遞歸其子元素指令,完成相同的操作。

其中,針對指令的transclude處理則需特殊說明:

if (directive.transclude === "element") {
            hasElementTranscludeDirective = true;
            var $originalCompileNode = $compileNode;
            $compileNode = attrs.$$element = $(document.createComment(" " + directive.name + ": " + attrs[directive.name] + " "));
            $originalCompileNode.replaceWith($compileNode);
            terminalPriority = directive.priority;
            childTranscludeFn = compile($originalCompileNode, terminalPriority);
          } else {
            var $transcludedNodes = $compileNode.clone().contents();
            childTranscludeFn = compile($transcludedNodes);
            $compileNode.empty();
          }

如果指令的transclude屬性設置為字符串“element”時,則會用注釋comment替換當前元素節(jié)點,再重新編譯原先的DOM節(jié)點,而如果transclude設置為默認的true時,則會繼續(xù)編譯其子節(jié)點,并通過transcludeFn傳遞編譯后的DOM對象,完成用戶自定義的DOM處理。

在返回的nodeLinkFn中,根據(jù)用戶指令的定義,如果指令帶有隔離作用域,則創(chuàng)建一個隔離作用域,并在當前的dom節(jié)點上綁定ng-isolate-scope類名,同時將隔離作用域緩存到dom節(jié)點上;

接下來,如果dom節(jié)點上某個指令定義了控制器,則會調用$cotroller服務,通過依賴注入的方式($injector.invoke)獲取該控制器的實例,并緩存該控制器實例;
隨后,調用initializeDirectiveBindings,完成隔離作用域屬性的單向綁定(@),雙向綁定(=)和函數(shù)的引用(&),針對隔離作用域的雙向綁定模式(=)的實現(xiàn),則是通過自定義的編譯器完成簡單Angular語法的編譯,在指定作用域下獲取表達式(標示符)的值,保存為lastValue,并通過設置parentValueFunction添加到當前作用域的$watch數(shù)組中,每次$digest循環(huán),判斷雙向綁定的屬性是否變臟(dirty),完成值的同步。

最后,根據(jù)applyDirectivesToNode第一步的初始化操作,將遍歷執(zhí)行指令compile函數(shù)返回的鏈接函數(shù)構造出成的preLinkFns和postLinkFns數(shù)組,依次執(zhí)行,如下所示:

_.forEach(preLinkFns, function(linkFn) {
          linkFn(
            linkFn.isolateScope ? isolateScope : scope,
            $element,
            attrs,
            linkFn.require && getControllers(linkFn.require, $element),
            scopeBoundTranscludeFn
          );
        });
        if (childLinkFn) {
          var scopeToChild = scope;
          if (newIsolateScopeDirective && newIsolateScopeDirective.template) {
            scopeToChild = isolateScope;
          }
          childLinkFn(scopeToChild, linkNode.childNodes, boundTranscludeFn);
        }
        _.forEachRight(postLinkFns, function(linkFn) {
          linkFn(
            linkFn.isolateScope ? isolateScope : scope,
            $element,
            attrs,
            linkFn.require && getControllers(linkFn.require, $element),
            scopeBoundTranscludeFn
          );
        });

可以看出,首先執(zhí)行preLinkFns的函數(shù);緊接著遍歷子節(jié)點的鏈接函數(shù),并執(zhí)行;最后執(zhí)行postLinkFns的函數(shù),完成當前dom元素的鏈接函數(shù)的執(zhí)行。指令的compile函數(shù)默認返回postLink函數(shù),可以通過compile函數(shù)返回一個包含preLink和postLink函數(shù)的對象設置preLinkFns和postLinkFns數(shù)組,如在preLink針對子元素進行DOM操作,效率會遠遠高于在postLink中執(zhí)行,原因在于preLink函數(shù)執(zhí)行時并未構建子元素的DOM,在當子元素是個擁有多個項的li時尤為明顯。

end of compile-publicLinkFn

終于,到了快結束的階段了。通過compileNodes返回從根節(jié)點(ng-app所在節(jié)點)開始的所有指令的最終合成鏈接函數(shù),最終在publicLinkFn函數(shù)中執(zhí)行。在publicLinkFn中,完成根節(jié)點與根作用域的綁定,并在根節(jié)點緩存指令的控制器實例,最終執(zhí)行合成鏈接函數(shù),完成了Angular最重要的編譯,鏈接兩個階段,從而開始了真正意義上的雙向綁定。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://systransis.cn/yun/78595.html

相關文章

  • angular源碼分析platformBrowserDynamic

    摘要:生成項目后,中的代碼這里調用了包中導出的函數(shù)這個函數(shù)是瀏覽器平臺的工廠函數(shù)執(zhí)行會返回瀏覽器平臺的實例函數(shù)是通過函數(shù)創(chuàng)建的這個函數(shù)接收個參數(shù)父平臺工廠函數(shù)平臺名稱服務提供商的數(shù)組顧名思義函數(shù)的作用是創(chuàng)建平臺工廠的函數(shù)在框架被加 cli生成項目后,main.ts中的代碼 import { enableProdMode } from @angular/core; import { platf...

    FWHeart 評論0 收藏0
  • angular源碼分析platformBrowserDynamic

    摘要:生成項目后,中的代碼這里調用了包中導出的函數(shù)這個函數(shù)是瀏覽器平臺的工廠函數(shù)執(zhí)行會返回瀏覽器平臺的實例函數(shù)是通過函數(shù)創(chuàng)建的這個函數(shù)接收個參數(shù)父平臺工廠函數(shù)平臺名稱服務提供商的數(shù)組顧名思義函數(shù)的作用是創(chuàng)建平臺工廠的函數(shù)在框架被加 cli生成項目后,main.ts中的代碼 import { enableProdMode } from @angular/core; import { platf...

    zhoutao 評論0 收藏0
  • Angular系列AoT編譯

    摘要:編譯在運行時才揭露它們,那樣有點太晚了。這是減少應用程序占用空間的最有效的技術之一。這將在未來得到改變。當前的最佳實踐是在開發(fā)器使用編譯,然后在發(fā)布產品前切換到編譯 概覽 眾所周知, angular應用在可執(zhí)行之前, angular應用中的組件和模板必須被轉化為可以被瀏覽器識別的javascript代碼, 而這種轉化正是通過angualr自身的編譯器所執(zhí)行的. angular提供了兩種...

    Object 評論0 收藏0
  • angular1.x 中重要指令介紹($eval,$parse和$compile)

    摘要:實際上就是這里要表現(xiàn)的其實是上下文的替換功能。這就是死模板了,而所謂的活模板,就是這里面的數(shù)據(jù)全部經過了數(shù)據(jù)的綁定會自動找到當前的上下文,來綁定數(shù)據(jù)。最后顯示出來的就是活模板,也就是經過數(shù)據(jù)綁定的模板。 這篇文章是我兩年前在博客園寫的,現(xiàn)在移植過來,不過Angular 1.x 在國內用的人已經不多了,希望能幫助到有需要的人 在 angular 的服務中,有一些服務你不得不去了解,因為他...

    terasum 評論0 收藏0
  • JavaScript 進階深入理解數(shù)據(jù)雙向綁定

    摘要:當我們的視圖和數(shù)據(jù)任何一方發(fā)生變化的時候,我們希望能夠通知對方也更新,這就是所謂的數(shù)據(jù)雙向綁定。返回值返回傳入函數(shù)的對象,即第一個參數(shù)該方法重點是描述,對象里目前存在的屬性描述符有兩種主要形式數(shù)據(jù)描述符和存取描述符。 前言 談起當前前端最熱門的 js 框架,必少不了 Vue、React、Angular,對于大多數(shù)人來說,我們更多的是在使用框架,對于框架解決痛點背后使用的基本原理往往關注...

    sarva 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<