先後端分手大師——MVVM 模式 從輸入cnblogs.com到博客園首頁徹底展現發生了什麼

先後端分手大師——MVVM 模式

 能實現先後端分離就是最大的優點了 之前都是後端代碼夾雜前端代碼 如今後端只需提供數據就行了

簡而言之

以前對 MVVM 模式一直只是模模糊糊的認識,正所謂沒有實踐就沒有發言權,經過這兩年對 Vue 框架的深刻學習和項目實踐,終於能夠裝B了有了撥開雲霧見月明的感受。javascript

Model–View–ViewModel(MVVM) 是一個軟件架構設計模式,由微軟 WPF 和 Silverlight 的架構師 Ken Cooper 和 Ted Peters 開發,是一種簡化用戶界面的事件驅動編程方式。由 John Gossman(一樣也是 WPF 和 Silverlight 的架構師)於2005年在他的博客上發表。css

MVVM 源自於經典的 Model–View–Controller(MVC)模式(期間還演化出了 Model-View-Presenter(MVP)模式,可忽略不計)。MVVM 的出現促進了 GUI 前端開發與後端業務邏輯的分離,極大地提升了前端開發效率。MVVM 的核心是 ViewModel 層,它就像是一箇中轉站(value converter),負責轉換 Model 中的數據對象來讓數據變得更容易管理和使用,該層向上與視圖層進行雙向數據綁定,向下與 Model 層經過接口請求進行數據交互,起呈上啓下做用。以下圖所示:html

MVVM模式前端

MVVM 已經至關成熟了,主要運用但不只僅在網絡應用程序開發中。KnockoutJS 是最先實現 MVVM 模式的前端框架之一,當下流行的 MVVM 框架有 Vue,Angular 等。java

組成部分

簡單畫了一張圖來講明 MVVM 的各個組成部分:webpack

MVVM分層示意圖web

分層設計一直是軟件架構的主流設計思想之一,MVVM 也不例外。面試

# View 層

View 是視圖層,也就是用戶界面。前端主要由 HTML 和 CSS 來構建,爲了更方便地展示 ViewModel 或者 Model 層的數據,已經產生了各類各樣的先後端模板語言,好比 FreeMarker、Marko、Pug、Jinja2等等,各大 MVVM 框架如 KnockoutJS,Vue,Angular 等也都有本身用來構建用戶界面的內置模板語言。ajax

# Model 層

Model 是指數據模型,泛指後端進行的各類業務邏輯處理和數據操控,主要圍繞數據庫系統展開。後端的處理一般會很是複雜:數據庫

先後端對比

後端:咱們這裏的業務邏輯和數據處理會很是複雜!
前端:關我屁事!

後端業務處理再複雜跟咱們前端也沒有半毛錢關係,只要後端保證對外接口足夠簡單就好了,我請求api,你把數據返出來,咱倆就這點關係,其餘都扯淡。

# ViewModel 層

ViewModel 是由前端開發人員組織生成和維護的視圖數據層。在這一層,前端開發者對從後端獲取的 Model 數據進行轉換處理,作二次封裝,以生成符合 View 層使用預期的視圖數據模型。須要注意的是 ViewModel 所封裝出來的數據模型包括視圖的狀態和行爲兩部分,而 Model 層的數據模型是隻包含狀態的,好比頁面的這一塊展現什麼,那一塊展現什麼這些都屬於視圖狀態(展現),而頁面加載進來時發生什麼,點擊這一塊發生什麼,這一塊滾動時發生什麼這些都屬於視圖行爲(交互),視圖狀態和行爲都封裝在了 ViewModel 裏。這樣的封裝使得 ViewModel 能夠完整地去描述 View 層。因爲實現了雙向綁定,ViewModel 的內容會實時展示在 View 層,這是激動人心的,由於前端開發者不再必低效又麻煩地經過操縱 DOM 去更新視圖,MVVM 框架已經把最髒最累的一塊作好了,咱們開發者只須要處理和維護 ViewModel,更新數據視圖就會自動獲得相應更新,真正實現數據驅動開發。看到了吧,View 層展示的不是 Model 層的數據,而是 ViewModel 的數據,由 ViewModel 負責與 Model 層交互,這就徹底解耦了 View 層和 Model 層,這個解耦是相當重要的,它是先後端分離方案實施的重要一環。

沒有什麼是一個栗子不能解決的

扯了這麼多,並無什麼卵用。千言萬語不如一個栗子來的乾脆,下面用一個 Vue 實例來講明 MVVM 的具體表現。

Vue 的 View 模板:

<div id="app"> <p>{{message}}</p> <button v-on:click="showMessage()">Click me</button> </div> 

Vue 的 ViewModel 層(下面是僞代碼):

var app = new Vue({ el: '#app', data: { // 用於描述視圖狀態(有基於 Model 層數據定義的,也有純前端定義) message: 'Hello Vue!', // 純前端定義 server: {}, // 存放基於 Model 層數據的二次封裝數據 }, methods: { // 用於描述視圖行爲(徹底前端定義) showMessage(){ let vm = this; alert(vm.message); } }, created(){ let vm = this; // Ajax 獲取 Model 層的數據 ajax({ url: '/your/server/data/api', success(res){ // TODO 對獲取到的 Model 數據進行轉換處理,作二次封裝 vm.server = res; } }); } }) 

服務端的 Model 層(省略業務邏輯處理,只描述對外接口):

{
    "url": "/your/server/data/api", "res": { "success": true, "name": "IoveC", "domain": "www.cnblogs.com" } } 

這就是完整的 MVVM 編程模式。

代碼執行以後雙向綁定的效果以下:

Vue實現的響應的數據綁定

嘿嘿,先後端能夠成功分手了,之後不再用關心後端個錘子開發進度\暴怒臉,複雜實現,blabla...,盡情享用前端如絲般順滑的開發快感吧:)

 

 

從輸入cnblogs.com到博客園首頁徹底展現發生了什麼

 

以前面試時候常常被問及這個問題,支支吾吾回答沒有底氣,仔細研究了一下,發現裏面學問還真很多。

從輸入 cnblogs.com 到博客園首頁徹底展示這個過程能夠大體分爲 網絡通訊 和 頁面渲染 兩個步驟。

網絡通訊走的五層因特網協議棧(OSI標準是七層模型,但實際實現一般是五層)。畫了一張圖:

五層因特網協議棧

DNS 解析成 IP 地址

DNS屬於應用層協議。客戶端會先檢查本地是否有對應的 ip 地址,若是有就返回,不然就會請求上級 DNS 服務器,知道找到或到根節點。這一過程可能會很是耗時,使用 dns-prefetch 可以使瀏覽器在空閒時提早將這些域名轉化爲 ip 地址,真正請求資源時就避免了這個過程的時間。例如京東首頁的處理:

京東首頁dns-prefetch處理

發送 http 請求

HTTP也是應用層協議。HTTP(HyperText Transport Protocol)定義了一個基於請求/響應模式的、無狀態的、應用層的協議,用於從萬維網服務器傳輸超文本到本地瀏覽器。絕大多數的Web開發,都是構建在HTTP協議之上的Web應用。客戶端組織併發送 http 請求報文,包含 method、url、host、cookie 等信息,下面是訪問博客園首頁時 http 請求報文的樣子:

GET https://www.cnblogs.com/ HTTP/1.1 Host: www.cnblogs.com Connection: keep-alive User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36 Upgrade-Insecure-Requests: 1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9 Cookie: __gads=ID=b62b1e22b7de2e02:T=1493954370:S=ALNI_MYRebVRavER2PJmwdeFwpl33ACNoQ; If-Modified-Since: Mon, 27 Nov 2017 12:21:04 GMT 

請求頭裏的每一個字段都有各自的做用,具體含義可查閱 http 協議相關文章。

TCP 傳輸報文

TCP 將 http 長報文劃分爲短報文,經過「三次握手」與服務器創建鏈接,進行可靠傳輸。「三次握手」創建鏈接的過程和打電話極像:

客戶端:喂,我要和 Server 通話
服務端:你好,我是 Server,你是 Client 嗎
客戶端:沒錯,我是 Client

鏈接創建成功,接下來就能夠正式傳送數據了。

數據傳完以後斷開tcp鏈接還要經過「四次揮手」,大概意思以下:

客戶端:Server 小寶貝,我話說完了,你掛電話吧
服務端:我不掛,我不掛,你先掛,你不掛我也不掛
---------------- Client 一陣無語 --------------
服務端:你掛了嗎
客戶端:行,那我先掛了

至此完成了一次完整的資源請求響應。

須要注意的是,瀏覽器對同一域名下併發的tcp鏈接數是有限制的,2個到10個不等。爲了解決這個資源加載瓶頸,有幾種流行的優化方案:

# 資源打包,合併請求

好比頁面樣式所有打包在一個 css 文件內,頁面邏輯所有打包在一個 js 文件內,圖片拼合成雪碧圖,這樣可有效減小頁面的資源請求數量。webpack 是時下最流行的模塊打包工具之一,它能夠將頁面內全部資源(包括js,css,圖片,字體等等)都打包進一個 js 文件,不明覺厲。

# 域名拆分,資源分散存儲

當瀏覽器向服務器請求一個靜態資源時,會先發送該域名下的 cookies,服務器對於這些 cookie 根本不會作任何處理,所以它們只是在毫無心義的消耗帶寬,因此應該確保對於靜態內容的請求是無 cookie 的請求(也就是所謂的 cookie-free)。將站點的 js、css、圖片等靜態文件放在一個專門的域名下訪問,因爲該域名與主站域名不一樣,因此瀏覽器就不會把主域名下的 cookies 傳給該域,從而減小網絡開銷,特別是細碎靜態文件特別多的狀況下效果顯著。

另外一方面,因爲瀏覽器是基於域名的併發鏈接數限制,而不是頁面。所以將資源部署在不一樣的域名下可使頁面的總併發鏈接數獲得線性提高。

# Connection: keep-alive,複用已創建的鏈接

在 http 早期,每一個 http 請求都要打開一個 tcp 鏈接,請求完就關閉這個鏈接,致使每一個請求都要來一遍「三次握手」和「四次揮手」,從而磨磨唧唧多出來大量無謂的等待時間。就比如出去吃飯,等飯等半個小時,端上來十分鐘吃完了,結帳排隊又等了半個小時,要是剛進來就吃現成的吃完就跑那多爽啊。keep-alive 乾的就是這件事,當第一個請求數據傳輸完畢以後,服務器說「客戶端你不要關閉這個鏈接,直接換下個請求,我不想再握你的破手了」。這樣下個請求就直接傳輸數據而不用先走「三次握手」的流程了。這比如你又去吃飯,吃你最喜歡的紅燒肉,飯店在今天第一個客人點紅燒肉的時候就炒了一大鍋紅燒肉,你點餐的時候直接吃現成的就好了,吃完直接跑,哈哈美滋滋。

# 控制緩存

將靜態資源強制緩存在客戶端,經過添加文件指紋等方式使客戶端只請求發生了變動的資源,可有效下降靜態資源請求數量。具體可參看前端靜態資源緩存控制策略

# 延遲加載,懶加載,按需加載

不少頁面瀏覽量雖然很大,但其實很大比例用戶掃完第一屏就直接跳走了,第一屏如下的內容用戶根本就不感興趣。 對於超大流量的網站,這個問題尤爲重要。這時可根據用戶的行爲進行按需加載,用戶用到了就去加載,用不到就不去加載。

以上都是從減小創建tcp鏈接數量的角度去優化頁面性能,以後會分享更多前端性能優化方面的實用方法。

IP 尋址

Internet Protocol 是定義網絡之間彼此互聯規則的協議,主要解決邏輯尋址和網絡通用數據傳輸格式兩個問題。

全部鏈接到因特網上的設備都會被分配一個惟一的 IP 地址,就像網購時填寫的收貨地址同樣。因爲一個網絡設備的 IP 地址能夠更換,可是 MAC 硬件地址(就像身份證號)通常是固定不變的,因此首先使用 ARP 協議來找到目標主機的 MAC 硬件地址。當通訊的雙方不在同一個局域網時,須要屢次中轉(路由器)才能找到最終的目標,在中轉的過程當中還須要經過下一個中轉站的 MAC 地址來搜索下一個中轉目標。

傳輸層傳來的 TCP 報文會在這一層被 IP 封裝成網絡通用傳輸格式——IP數據包,IP 數據包是真正在網絡間進行傳輸的數據基本單元。

經過邏輯尋址定位到前面應用層 DNS 解析出來的 IP 地址的主機網絡位置,而後把數據以 IP 數據包的格式發送到那去。

封裝成幀

數據鏈路層負責將 IP 數據包封裝成適合在物理網絡上傳輸的幀格式並傳輸。設計數據鏈路層的主要目的就是在原始的、有差錯的物理傳輸線路的基礎上,採起差錯檢測、差錯控制與流量控制等方法,將有差錯的物理線路改進成邏輯上無差錯的數據鏈路,向網絡層提供高質量的服務。當採用複用技術時,一條物理鏈路上能夠有多條數據鏈路。

物理傳輸

上面這麼多層其實都是在爲不一樣的目的對要傳輸的數據進行封裝處理,而物理層則是經過各類傳輸介質(雙絞線,電磁波,光纖等)以信號的形式將上面各層封裝好的數據物理傳送過去。

至此一個 http 請求漂洋過海終於到達了服務器,接下來就是從物理層到應用層向上傳遞,將封裝的數據一層層剝開,服務器在應用層拿到最原始的請求信息後快速處理完,而後就開始向客戶端發送響應信息。此次是以服務器爲起點,客戶端爲終點再走一遍五層協議棧。

服務器的響應消息跋山涉水終於到達了瀏覽器,接下來就是頁面渲染(更具體可參看瀏覽器內部工做原理)。

 

頁面的渲染工做主要由瀏覽器的渲染引擎來完成(這裏以Chrome爲例)。

頁面渲染主流程

下面是渲染引擎在取得內容後的基本流程:

解析html構建dom樹 -> 解析css構建render樹 -> 佈局render樹 -> 繪製render樹

渲染引擎首先開始解析html,並將標籤轉化爲dom樹中的dom節點。接着,它解析外部css文件及style標籤中的樣式信息,這些樣式信息以及html標籤中的可見性指令將被用來構建另外一棵樹——render樹。render樹構建好了以後,將會執行佈局過程,該過程將肯定render樹每一個節點在屏幕上的確切座標。最後是繪製render樹,即遍歷render樹的每一個節點並將它們繪製到屏幕上。

偷了一張圖片(Chrome和Safari所用內核webkit頁面渲染主流程):


webkit頁面渲染主流程

爲了更好的用戶體驗,渲染引擎將會盡量早地將內容繪製在屏幕上,而不會等到全部的html都解析完成後再去構建、佈局和繪製render樹,它是解析完一部份內容就繪製一部份內容,同時可能還在經過網絡下載其他內容(圖片,腳本,樣式表等)。好比說,瀏覽器在代碼中發現一個 img 標籤引用了一張圖片,因而就向服務器發出圖片請求,此時瀏覽器不會等到圖片下載完,而是會繼續解析渲染後面的代碼,等到服務器返回圖片文件,因爲圖片佔用了必定面積,影響了後面段落的佈局,瀏覽器就會回過頭來從新渲染這部分代碼。

dom樹和render樹的關係

render樹節點和dom樹節點相對應,但這種對應關係不是一對一的,不可見的dom元素不會被插入render樹,例如head元素、script元素等。另外,display屬性爲none的元素也不會在渲染樹中出現(visibility屬性爲hidden的元素將出如今渲染樹中,這是由於visibility屬性爲hidden的元素雖然不可見但保留了元素的佔位)。

又偷了一張圖:

render樹與dom樹

佈局render樹(layout)

當渲染對象被建立並添加到render樹後,它們並無位置和大小,計算這些值的過程稱爲layout(佈局)。

佈局的座標系統相對於根渲染對象(它對應文檔的html標籤,可用 document.documentElement 拿到),使用top和left座標。根渲染對象的位置是 (0,0),它的大小是viewport即瀏覽器窗口的可見部分。佈局是一個遞歸的過程,由根渲染對象開始,而後遞歸地經過一些或全部的層級節點,爲每一個須要幾何信息的渲染對象進行計算。

爲了避免由於每一個小變化都所有從新佈局,瀏覽器使用一個 dirty bit(頁面重寫標誌位)系統,一個渲染對象發生了變化或是被添加了,就標記它及它的children爲dirty——須要layout。

當layout在整棵渲染樹觸發時,稱爲全局layout,這可能在下面這些狀況下發生:

  • 一個全局的樣式改變影響全部的渲染對象,好比字號的改變。
  • 窗口resize。

layout也能夠是增量的,這樣只有標誌爲dirty的渲染對象會從新佈局(也將致使一些額外的佈局)。增量layout會在渲染對象dirty時異步觸發,例如,當網絡接收到新的內容並添加到dom樹後,新的渲染對象會添加到render樹中。

繪製(paint)

繪製階段,遍歷render樹並調用渲染對象的paint方法將它們的內容顯示在屏幕上。和佈局同樣,繪製也能夠是全局的(繪製完整的樹)或增量的。在增量的繪製過程當中,一些渲染對象以不影響整棵樹的方式改變,改變的渲染對象使其在屏幕上的矩形區域失效(invalidate),這將致使操做系統將其看做dirty區域,併產生一個paint事件,操做系統很巧妙的處理這個過程,並將多個區域合併爲一個。

瀏覽器老是試着以最小的動做響應一個變化,因此一個元素顏色的變化將只致使該元素的重繪,元素位置的變化將致使元素的佈局和重繪,添加一個dom節點,也會致使這個元素的佈局和重繪。一些主要的變化,好比增長html元素的字號,將會致使緩存失效,從而引發整個render樹的佈局和重繪。

等到繪製完畢,頁面就徹底地展示在咱們面前了。

看似再簡單不過的操做,背後支撐的技術鏈已經複雜到不可想象。上面只是粗淺的輪廓,其中的每一步深挖進去都是一門大學問。不過我們前端了解一下就好了,不必較這個勁,否則就捨本逐末了。

相關文章
相關標籤/搜索