摘要:上述代碼采用了依賴(lài)注入的方式注入了五個(gè)服務(wù),分別用于實(shí)現(xiàn)依賴(lài)注入的注入器,代碼解析器,控制器服務(wù)根作用域服務(wù)和指令解析服務(wù)。緊接著,執(zhí)行函數(shù),執(zhí)行指令相關(guān)操作,并返回處理后的鏈接函數(shù)。
@(Angular)
$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)的文檔。
前面廢話一大堆,主要還是為后面的介紹做鋪墊。在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.$getthis.$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ū)ο螅⒈4嬖诠?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名,判斷是否滿足指令定義。
緊接著,執(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í)行用戶定義指令的相關(guān)操作。
首先則是初始化相關(guān)屬性,通過(guò)遍歷節(jié)點(diǎn)的所有指令,針對(duì)每個(gè)指令,依次判斷$$start屬性,優(yōu)先級(jí),隔離作用域,控制器,transclude屬性判斷并編譯其模板,構(gòu)建元素的DOM結(jié)構(gòu),最終執(zhí)行用戶定義的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ì)象,完成用戶自定義的DOM處理。
在返回的nodeLinkFn中,根據(jù)用戶指令的定義,如果指令帶有隔離作用域,則創(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)始了真正意義上的雙向綁定。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/78595.html
摘要:生成項(xiàng)目后,中的代碼這里調(diào)用了包中導(dǎo)出的函數(shù)這個(gè)函數(shù)是瀏覽器平臺(tái)的工廠函數(shù)執(zhí)行會(huì)返回瀏覽器平臺(tái)的實(shí)例函數(shù)是通過(guò)函數(shù)創(chuàng)建的這個(gè)函數(shù)接收個(gè)參數(shù)父平臺(tái)工廠函數(shù)平臺(tái)名稱(chēng)服務(wù)提供商的數(shù)組顧名思義函數(shù)的作用是創(chuàng)建平臺(tái)工廠的函數(shù)在框架被加 cli生成項(xiàng)目后,main.ts中的代碼 import { enableProdMode } from @angular/core; import { platf...
摘要:生成項(xiàng)目后,中的代碼這里調(diào)用了包中導(dǎo)出的函數(shù)這個(gè)函數(shù)是瀏覽器平臺(tái)的工廠函數(shù)執(zhí)行會(huì)返回瀏覽器平臺(tái)的實(shí)例函數(shù)是通過(guò)函數(shù)創(chuàng)建的這個(gè)函數(shù)接收個(gè)參數(shù)父平臺(tái)工廠函數(shù)平臺(tái)名稱(chēng)服務(wù)提供商的數(shù)組顧名思義函數(shù)的作用是創(chuàng)建平臺(tái)工廠的函數(shù)在框架被加 cli生成項(xiàng)目后,main.ts中的代碼 import { enableProdMode } from @angular/core; import { platf...
摘要:編譯在運(yùn)行時(shí)才揭露它們,那樣有點(diǎn)太晚了。這是減少應(yīng)用程序占用空間的最有效的技術(shù)之一。這將在未來(lái)得到改變。當(dāng)前的最佳實(shí)踐是在開(kāi)發(fā)器使用編譯,然后在發(fā)布產(chǎn)品前切換到編譯 概覽 眾所周知, angular應(yīng)用在可執(zhí)行之前, angular應(yīng)用中的組件和模板必須被轉(zhuǎn)化為可以被瀏覽器識(shí)別的javascript代碼, 而這種轉(zhuǎn)化正是通過(guò)angualr自身的編譯器所執(zhí)行的. angular提供了兩種...
摘要:實(shí)際上就是這里要表現(xiàn)的其實(shí)是上下文的替換功能。這就是死模板了,而所謂的活模板,就是這里面的數(shù)據(jù)全部經(jīng)過(guò)了數(shù)據(jù)的綁定會(huì)自動(dòng)找到當(dāng)前的上下文,來(lái)綁定數(shù)據(jù)。最后顯示出來(lái)的就是活模板,也就是經(jīng)過(guò)數(shù)據(jù)綁定的模板。 這篇文章是我兩年前在博客園寫(xiě)的,現(xiàn)在移植過(guò)來(lái),不過(guò)Angular 1.x 在國(guó)內(nèi)用的人已經(jīng)不多了,希望能幫助到有需要的人 在 angular 的服務(wù)中,有一些服務(wù)你不得不去了解,因?yàn)樗?..
摘要:當(dāng)我們的視圖和數(shù)據(jù)任何一方發(fā)生變化的時(shí)候,我們希望能夠通知對(duì)方也更新,這就是所謂的數(shù)據(jù)雙向綁定。返回值返回傳入函數(shù)的對(duì)象,即第一個(gè)參數(shù)該方法重點(diǎn)是描述,對(duì)象里目前存在的屬性描述符有兩種主要形式數(shù)據(jù)描述符和存取描述符。 前言 談起當(dāng)前前端最熱門(mén)的 js 框架,必少不了 Vue、React、Angular,對(duì)于大多數(shù)人來(lái)說(shuō),我們更多的是在使用框架,對(duì)于框架解決痛點(diǎn)背后使用的基本原理往往關(guān)注...
閱讀 3205·2021-11-08 13:21
閱讀 1195·2021-08-12 13:28
閱讀 1406·2019-08-30 14:23
閱讀 1924·2019-08-30 11:09
閱讀 840·2019-08-29 13:22
閱讀 2684·2019-08-29 13:12
閱讀 2549·2019-08-26 17:04
閱讀 2250·2019-08-26 13:22