亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Angular中$compile源碼分析

 更新時(shí)間:2016年01月28日 10:08:04   投稿:hebedich  
本文給大家分享的是通過(guò)angular中的$compile源碼進(jìn)行分析,從而更好的理解angular的使用,非常的不錯(cuò),希望大家能夠喜歡。

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

[TOC]

Angular的compileProvider

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

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

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

compileProvider.\$get

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

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

\$get方法最終返回compile函數(shù),compile函數(shù)就是\$compile服務(wù)的具體實(shí)現(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;
   };
  }

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

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

通過(guò)上一小結(jié),可以看出\$compile服務(wù)的核心在于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;
  }

代碼有些長(zhǎng),我們一點(diǎn)一點(diǎn)分析。

首先,linkFns數(shù)組用于存儲(chǔ)每個(gè)DOM節(jié)點(diǎn)上所有指令的處理后的鏈接函數(shù)和子節(jié)點(diǎn)上所有指令的處理后的鏈接函數(shù),具體使用遞歸的方式實(shí)現(xiàn)。隨后,在返回的compositeLinkFn中,則是遍歷linkFns,針對(duì)每個(gè)鏈接函數(shù),創(chuàng)建起對(duì)應(yīng)的作用域?qū)ο螅ㄡ槍?duì)創(chuàng)建隔離作用域的指令,創(chuàng)建隔離作用域?qū)ο?,并保存在?jié)點(diǎn)的緩存中),并處理指令是否設(shè)置了transclude屬性,生成相關(guān)的transclude處理函數(shù),最終執(zhí)行鏈接函數(shù);如果當(dāng)前指令并沒(méi)有鏈接函數(shù),則調(diào)用其子元素的鏈接函數(shù),完成當(dāng)前元素的處理。

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

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

applyDirectivesToNode函數(shù)

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

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

其中,針對(duì)指令的transclude處理則需特殊說(shuō)明:

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屬性設(shè)置為字符串“element”時(shí),則會(huì)用注釋comment替換當(dāng)前元素節(jié)點(diǎn),再重新編譯原先的DOM節(jié)點(diǎn),而如果transclude設(shè)置為默認(rèn)的true時(shí),則會(huì)繼續(xù)編譯其子節(jié)點(diǎn),并通過(guò)transcludeFn傳遞編譯后的DOM對(duì)象,完成用戶(hù)自定義的DOM處理。

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

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

最后,根據(jù)applyDirectivesToNode第一步的初始化操作,將遍歷執(zhí)行指令compile函數(shù)返回的鏈接函數(shù)構(gòu)造出成的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é)點(diǎn)的鏈接函數(shù),并執(zhí)行;最后執(zhí)行postLinkFns的函數(shù),完成當(dāng)前dom元素的鏈接函數(shù)的執(zhí)行。指令的compile函數(shù)默認(rèn)返回postLink函數(shù),可以通過(guò)compile函數(shù)返回一個(gè)包含preLink和postLink函數(shù)的對(duì)象設(shè)置preLinkFns和postLinkFns數(shù)組,如在preLink針對(duì)子元素進(jìn)行DOM操作,效率會(huì)遠(yuǎn)遠(yuǎn)高于在postLink中執(zhí)行,原因在于preLink函數(shù)執(zhí)行時(shí)并未構(gòu)建子元素的DOM,在當(dāng)子元素是個(gè)擁有多個(gè)項(xiàng)的li時(shí)尤為明顯。

end of compile-publicLinkFn

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

相關(guān)文章

  • angularjs1.5 組件內(nèi)用函數(shù)向外傳值的實(shí)例

    angularjs1.5 組件內(nèi)用函數(shù)向外傳值的實(shí)例

    今天小編就為大家分享一篇angularjs1.5 組件內(nèi)用函數(shù)向外傳值的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-09-09
  • AngularJS基礎(chǔ)知識(shí)筆記之過(guò)濾器

    AngularJS基礎(chǔ)知識(shí)筆記之過(guò)濾器

    在我們開(kāi)發(fā)中經(jīng)常需要在頁(yè)面顯示給用戶(hù)的信息需要一定處理格式化,才能顯示給用戶(hù)。比如時(shí)間本地化,或者yyyy-MM-dd HH:mm:ss格式,數(shù)字精度格式化,本地化,人名格式化等等。在angularjs中為我們提供了叫filter的指令,讓我們能夠很輕易就能做到著一些列的功能
    2015-05-05
  • Angular 4.0學(xué)習(xí)教程之架構(gòu)詳解

    Angular 4.0學(xué)習(xí)教程之架構(gòu)詳解

    作為一種大受歡迎的Web應(yīng)用程序框架,Angular終于迎來(lái)了版本4.0,下面這篇文章主要給大家介紹了關(guān)于Angular 4.0學(xué)習(xí)教程之架構(gòu)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來(lái)一起看看吧。
    2017-09-09
  • 詳解Angular路由之路由守衛(wèi)

    詳解Angular路由之路由守衛(wèi)

    這篇文章主要介紹了詳解Angular路由之路由守衛(wèi),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-05-05
  • 關(guān)于angular瀏覽器兼容性問(wèn)題的解決方案

    關(guān)于angular瀏覽器兼容性問(wèn)題的解決方案

    這篇文章主要給大家介紹了關(guān)于angular瀏覽器兼容性問(wèn)題的解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用angular具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • AnjularJS中$scope和$rootScope的區(qū)別小結(jié)

    AnjularJS中$scope和$rootScope的區(qū)別小結(jié)

    這篇文章給大家整理了關(guān)于AnjularJS中$scope和$rootScope的區(qū)別,文中運(yùn)用實(shí)例代碼介紹的很詳細(xì),有需要的朋友們可以參考借鑒,下面來(lái)一起看看吧。
    2016-09-09
  • 詳解AngularJS中的依賴(lài)注入機(jī)制

    詳解AngularJS中的依賴(lài)注入機(jī)制

    這篇文章主要介紹了詳解AngularJS中的依賴(lài)注入機(jī)制,對(duì)JavaScript各組件的使用起到非常重要的作用,需要的朋友可以參考下
    2015-06-06
  • AngularJS用戶(hù)選擇器指令實(shí)例分析

    AngularJS用戶(hù)選擇器指令實(shí)例分析

    這篇文章主要介紹了AngularJS用戶(hù)選擇器指令,結(jié)合實(shí)例形式分析了angular指令實(shí)現(xiàn)選擇器功能的具體操作步驟與相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2016-11-11
  • angular.js + require.js構(gòu)建模塊化單頁(yè)面應(yīng)用的方法步驟

    angular.js + require.js構(gòu)建模塊化單頁(yè)面應(yīng)用的方法步驟

    這篇文章主要給大家介紹了關(guān)于利用angular.js + require.js構(gòu)建模塊化單頁(yè)面應(yīng)用的方法步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。
    2017-07-07
  • angularjs在ng-repeat中使用ng-model遇到的問(wèn)題

    angularjs在ng-repeat中使用ng-model遇到的問(wèn)題

    本文給大家分享了一個(gè)個(gè)人在使用angular過(guò)程中遇到的在ng-repeat中使用ng-model的問(wèn)題,并附上簡(jiǎn)單的解決辦法,希望能對(duì)大家學(xué)習(xí)angular有所幫助
    2016-01-01

最新評(píng)論