天天看點

Angular2:從AngularJS 1.x 中學到的經驗

Angular2:從AngularJS 1.x 中學到的經驗

controller

angularjs 1.x 遵從了model view controller (mvc)的微架構模式。有人會争論說,它看起來更像 model view viewmodel (mvvm),因為controller 有自己獨立的文法,而視圖資料模型是作為scope 或者目前上下文的屬性而存在的。但是,如果我們使用model view presenter 模式(mvp)也能實作同樣的目的。由于可以用各種不同的形式來組織應用邏輯,是以核心團隊把angularjs 1.x 稱為model view whatever (mvw)架構。

在任何angularjs 應用程式中,視圖(view)都應該是由指令組而成的。各種指令互相協作,進而實作功能完整的使用者界面。服務(service)負責封裝應用的業務邏輯。在服務代碼中,我們可以通過http 與 restful 服務進行通訊,使用websocket 甚至使用webrtc 進行實時通訊。對于我們的應用來說,服務是實作領域模型和業務規則的基礎構件。還有另外一個元件就是控制器(controller),它主要負責處理使用者輸入并把執行過程代理給對應的服務。

雖然服務和指令都有明确的角色定義,但是在ios 應用中,我們常常會看到massiveview controller 這種反模式。有時候,開發者會嘗試在控制器中通路甚至直接修改dom。一開始的時候,這種方式用來實作一些很簡單的功能,例如修改标簽的大小,或者快速粗暴地修改标簽的樣式。另一個值得注意的反模式就是:在不同的控制器中重複實作相同的業務邏輯。開發者傾向于拷貝粘貼這些邏輯,而實際上這些東西應該封裝到service 裡面去。

建構angularjs 應用的最佳實踐是:控制器根本不應該操作dom,而是應該把通路和

操作dom 的邏輯分離到指令中去。如果控制器之間有一些重複的邏輯,最大的可能就是:我們需要把這些邏輯封裝到某個服務裡面,如果某個控制器需要用到這些功能,就使用angularjs 的依賴注入機制注入這個服務。

以上就是我們從angularjs 1.x 中所學習到的内容。這樣看來,似乎控制器的功能應該移到指令内部的控制器中去。由于指令支援依賴注入api,是以在接收到使用者的輸入之後,可以直接把具體的操作代理給注入的服務來執行。基于這一原因,angular 2 中采用了完全不同的實作方案,删除了ng-controller 指令,解決了濫用該指令導緻控制器滿天飛的情況。

在《邁向angular2》第4 章,将會學習如何用angular 2中的元件和指令來取代angularjs1.x 中控制器的功能。

scope

angularjs 中的資料綁定機制是利用scope 對象來實作的。我們首先在scope 對象上添加各種屬性,然後在模闆中顯式聲明需要綁定這些屬性(單向綁定或者雙向綁定都可以)。這種方案看起來很清晰,但是scope 還有兩個更重要的職責:派發事件和實作基于髒值檢測的行為。angular 初學者需要花費大量精力去了解什麼是scope 以及怎麼使用scope。是以,angularjs 1.2 引入了一個叫controller as syntax 的概念。它允許我們直接在控制器内部為目前上下文(this)添加屬性,而不需要顯式注入scope 對象然後再在上面添加屬性。以下代碼片段示範了這種簡化的文法:

Angular2:從AngularJS 1.x 中學到的經驗

angular 2 更進一步,直接删除了scope 對象。所有表達式都在特定ui 元件的上下文

中執行。把scope api 整體删掉之後使得angular 2 得到了大幅度簡化,我們不再需要顯式注入scope 了,隻要把屬性直接添加到ui 元件上,然後再進行綁定操作即可。這種api 讓人感覺更簡單也更自然。

在《邁向angular2》一書第4 章會詳細學習元件和髒值檢測機制。

依賴注入

在javascript 領域,angularjs 1.x 也許是市面上的第一個通過dependencyinjection (di)引入inversion of control (ioc)機制的架構。di 可以帶來很多好處,比如:易測試性、更好的代碼結構和子產品化,以及更簡潔明了。雖然在1.x 版本中di 運作得相當不錯,但是angular 2 對它進行了進一步的發揮。因為 angular 2 是基于最新web 标準建構的,是以它使用了ecmascript 2016 裝飾器(decorator)文法對使用di的代碼進行了注解。這裡的裝飾器與python 中的裝飾器或java 中的注解非常類似。它們都可以使用反射機制來decorate(裝飾)指定對象的行為。由于裝飾器還沒有标準化,也不被主流浏覽器所支援,是以使用的時候需要經過中間轉換步驟。如果你不想這麼麻煩,也可以直接用ecmascript 5 文法編寫一些冗長的代碼去實作相同的語義。

新版本的di 更靈活、功能更豐富,也消除了angularjs 1.x 中的一些誤區,例如api 不統一的問題。在 1.x 中,有些對象是根據參數的位置順序注入的(例如scope、标簽、屬性,以及指令link 函數中的控制器);而其他對象則是根據名稱注入的(例如在控制器,指令,服務和過濾器中會根據參數名稱進行注入)。

在《邁向angular2》一書第5 章會進一步學習依賴注入api。

服務端渲染

web 需求越大,web 應用就變得越複雜。建構一個真實的單頁應用需要編寫大量的javascript 代碼,把用到的所有外部類庫全部一次性包含進來會導緻頁面上腳本的體積增加到好幾兆。在移動裝置上初始化應用可能要用幾秒到十幾秒的時間:從服務端擷取所有資源、解析并執行javascript、渲染頁面、應用所有樣式。如果在低端移動裝置上使用無線網絡,這個過程可能會讓使用者放棄通路應用。雖然可以用一些技巧來加速這個過程,但是在複雜的應用中,沒有銀彈。

在嘗試提升使用者體驗的過程中,開發者們發現了所謂的server-side rendering(服務端渲染)技術。它可以把單頁應用中所請求的某個視圖在服務端渲染好,然後把對應的html 直接發送給使用者。随後,在所有資源處理完畢之後,腳本就會添加事件監聽器并進行資料綁定操作。這樣做看起來像是一個提升應用性能的好方法。使用此方法的先驅之一是reactjs,它利用了node.js 的dom 實作在服務端預先渲染使用者界面。可惜的是,angularjs 1.x 的構架不支援這種特性。原因是架構和浏覽器api 緊密耦合在一起,在webworker 中進行髒值檢測的時候我們也遇到過同樣的問題。

服務端渲染的另一個典型使用場景就是:建構對search engine optimization(seo,搜尋引擎優化)友好的應用。為了讓angularjs 1.x 應用能夠被搜尋引擎索引,目前已經出現了很多hack 方法。例如,其中一種實戰案例是這麼處理的:使用無前端浏覽器漫遊整個應用,執行每個頁面上的腳本并把渲染結果緩存成html 檔案,進而讓搜尋引擎能夠通路應用。

雖然這種變通方案可以建構對seo 友好的應用,但是采用服務端渲染技術可以同時解決之前提到的兩個問題:一是提升使用者體驗;二是用更簡單優雅的方式來建構對seo 友好的應用。

隻要把angular 2 和dom 進行解耦,我們的應用就可以在浏覽器之外的環境中運作了。為了實作這一目的,社群已經開發了一款工具,首先在服務端預先渲染單頁應用中的視圖,然後再轉發給浏覽器。本書在編寫這段内容的時候,這款工具仍然處在開發的早期階段,是以它并沒有被包含在架構的核心中。

在《邁向angular2》第8 章,我們将會深入學習這款工具。

大規模應用

自從backbone.js 出現之後 ,mvw 就是建構單頁應用的标配。我們可以按照注意點分離原則把業務邏輯從視圖中分離出來,進而建構出設計良好的應用。mvm 可以使用觀察者模式監聽資料模型的改變,當發生改變的時候重新整理視圖。但是,其中的事件處理器之間存在一些顯式或者隐式的依賴,這就使得應用中的資料流不清晰且難以了解。在angularjs 1.x 中,不同的螢幕之間可以互相依賴,進而導緻了digest 循環必須進行若幹次周遊,這些表達式的結果才能最終趨于穩定。是以,angular 2 采用了單向資料流設計,優點如下:

 更明确的資料流。

 不同的資料綁定之間沒有依賴關系,是以digest 沒有存活時間(ttl)的概念。

 性能更高:① digest 循環隻運作一次。②建立對immutable/observable (不可變/可觀察)資料模型友好的應用程式,進而可以做深度優化。

資料流的改變為augularjs 1.x 基礎構架帶來了又一項根本性的變革。

當需要維護一個用javascript 編寫的龐大的代碼庫時,我們可能要換一個角度來看資料流的問題。雖然javascript 的鴨子類型(指js 對象的動态特性——譯者注)讓這門語言非常靈活,但是同時也讓ide 和文本編輯器很難對代碼進行分析和支援。對大型項目進行代碼重構變得很難而且容易出錯,原因是在大多數情況下進行靜态分析和類型推斷是不可能的。同時,在缺少編譯器的情況下,很容易出現錯别字,在跑測試用例或者真正運作應用之前很難發現這些錯誤。

Angular2:從AngularJS 1.x 中學到的經驗

angular 核心團隊決定使用typescript ,因為它有更好的工具,還有編譯時類型檢查;使用typescript 有助于提升生産效率,還能減少出錯。如上圖所述,typescript是ecmascript 的超集,它引入了顯式類型注解和編譯器。typescript 代碼會被編譯成目前浏覽器所支援的普通的javascript。typescript 從1.6 版開始,已經實作了ecmascript 2016 裝飾器,它是angular 2 的完美選擇。

各種ide 和文本編輯器都可以更好地對typescript 進行靜态代碼分析和類型檢查。所有這些優點都可以減少出錯的機率,進而極大地提升生産率,同時還可以簡化代碼重構過程。typescript 另一個重要的隐含優點是使用靜态類型帶來的性能提升,因為javascript 虛拟機可以對靜态類型進行運作時優化。

在在《邁向angular2》第3 章中我們将詳細讨論typescript。

模闆

模闆是angularjs 1.x 的核心特性之一。模闆是簡單的html 并且不需要中間的處理和編譯過程,這一點與mustache 之類的大多數模闆引擎不同。angularjs 中的模闆簡潔而強大,我們可以在模闆内部建立domain specific language(dsl,領域模組化語言)來擴充html,還可以使用自定義标簽和屬性。

當然,這也是web component 背後的主要目标之一。前面我們已經提到過angular 2是怎麼使用這一新技術的以及為什麼要使用它的原因。盡管angularjs 1.x 中的模闆很強大,但是還有很大的改進空間!angular 2 中的模版吸取了上一個版本中的精華,解決了一些讓人困惑的問題,增強了模闆的功能。

假設我們建立了一個指令,允許使用者通過标簽的attribute 給它傳遞一個成員屬性。在angularjs 1.x 中,有以下三種不同的實作方法:

Angular2:從AngularJS 1.x 中學到的經驗

如果我們有一個user 指令,然後需要給它傳遞name 屬性,有三種不同的方法可以實作(這裡的意思看起來和上一段的末尾有一點重複,原文如此——譯者注):第一種方法是傳遞一個字面量(在這個例子裡面,也就是"literal");第二種方法是傳遞一個字元串,這個字元串可以當成表達式來執行(在這個例子裡面,也就是"expression");第三種方法是在{{}}中傳遞一個表達式。應該使用哪一種文法完全由指令的具體實作來決定,這就使得指令的api 變成一團亂麻并且難以記憶。

在日常工作中,處理大量基于不同的設計方案而開發的元件是一件令人沮喪的事情。為了解決這些問題,我們需要引入一種通用的約定。但是,為了取得良好的結果并保持api 的一緻性,需要整個社群達成一緻。

angular 2 為屬性提供了特殊的文法來解決這個問題,屬性值會在目前元件的上下文中執行,同時為傳遞字面量提供了不同的文法。

根據從angularjs 1.x 中獲得的經驗,還有一件事情我們已經習慣了,那就是模闆指令裡面使用的微文法(microsyntax ),如ng-if、nf-for。舉個例子,在 angularjs1.x 中,如果需要周遊一個使用者清單并展示使用者姓名,我們可以這樣做:

Angular2:從AngularJS 1.x 中學到的經驗

雖然這種文法看起來很直覺,但是隻有有限的工具能支援它。是以,angular 2 引入了更明确的文法來解決這個問題,同時語義上也更豐富:

Angular2:從AngularJS 1.x 中學到的經驗

以上代碼明确定義了一個(user)屬性,這個屬性将會在疊代(users)的上下文中建立。

但是,這種文法在輸入的時候顯得太冗長。是以,開發者可以使用以下簡化文法,然後再編譯成更冗長的形式:

Angular2:從AngularJS 1.x 中學到的經驗
Angular2:從AngularJS 1.x 中學到的經驗
文本編輯器和ide 可以為改進型的新模闆提供更進階的工具支援。在《邁向angular2》第4 章angular 2 中的元件和指令中,我們會讨論angular 2 中的模闆。

髒值檢測

在關于webworker 的小節中,我們已經提到過:在webworker 執行個體化出來的其他線程上下文中運作digest 循環的時機。利用javascript 虛拟機的代碼優化機制可以獲得顯著的性能提升,其中一種優化叫做内聯緩存。但是angularjs 1.x 中實作的digest循環記憶體使用效率不高,而且阻礙了這種優化過程。angular 團隊在這方面做了許多的研究,發現了提升digest 循環性能和效率的各種方法。這些發現推動了全新的髒值檢測機制的開發。

為了獲得更大的靈活性,angular 團隊把髒值檢測機制提取了出來,并且與架構核心進行了解耦。這樣一來就可以開發出不同的髒值檢測政策,在不同的環境中可以采用不同的政策。

最終結果就是:angular 2 中有兩種内置髒值檢測機制:

動态髒值檢測:與angularjs 1.x 中的髒值檢測機制類似。用于不允許eval()的系統中,如csp 插件和chrome 插件。

jit 髒值檢測:運作時動态生成髒值檢測代碼,允許 javascript 虛拟機進行深度代碼優化。

《邁向angular2》第4 章,會學習到新的髒值檢測機制以及它們的配置方法。
Angular2:從AngularJS 1.x 中學到的經驗

到此,我們讨論了為什麼需要使用最新版的javascript 語言;為什麼要使用web component 和webworker;以及為什麼不值得在1.x 版本中整合所有這些強大的工具。新架構層出不窮,好不好用隻有自己踩過坑才會知道。

Angular2:從AngularJS 1.x 中學到的經驗

繼續閱讀