目錄
一、移動端跨平臺開發方案
Hybrid App
React Native
Weex
Flutter?
PWA (Progressive Web App)
小程序
Cordova
html5
組件和模塊的區別
組件化
模塊化
前端代碼規范
前端工程化理解
網站性能監測與優化策略
1.網絡傳輸性能優化
頁面加載時,大致可以分為以下幾個步驟:
1.1.瀏覽器緩存
1.2.資源打包壓縮
1.3.圖片資源優化
1.4.網絡傳輸性能檢測工具——Page Speed
1.5.使用CDN
2.頁面渲染性能優化
2.1.瀏覽器渲染過程(Webkit)
瀏覽器渲染的流程如下:
2.2.DOM渲染層與GPU硬件加速
2.3.重排與重繪
常見引起重排屬性和方法
瀏覽器的渲染隊列:
強制刷新隊列:
重排優化建議
1. 分離讀寫操作
2. 樣式集中改變
3. 緩存布局信息
4. 離線改變dom
5. position屬性為absolute或fixed
6. 優化動畫
8.壓縮DOM的深度
9.圖片在渲染前指定大小
3.JS阻塞性能
js方面的性能優化小建議
4.【拓展】負載均衡
4.2.pm2實現Node.js“多進程”
4.3.nginx搭建反向代理
RESTful架構
資源(Resources)
表現層(Representation)
狀態轉化(State Transfer)
綜述
誤區
一、移動端跨平臺開發方案
Hybrid App
我們可以把純前端技術開發的 Web 網頁稱為 Web App,純原生技術開發的應用稱為 Native App,它們各有優缺點:純 Native 的迭代太慢,不能動態更新,且不能跨平臺,而純 Web 則有很多功能無法實現,雖然其動畫效果體驗差強人意,但跟原生比還是有些差距。
Hybrid App(混合應用)是指介于這兩者之間的 App,兼具“Native App 良好用戶交互體驗的優勢”和“Web App 跨平臺開發的優勢”。
Hybrid 的技術本質是在 WebView 的基礎上,與原生客戶端建立 JS Bridge 橋接,以達到 JS 調用 Native API 和 Native 執行 JS 方法的目的。
React Native
- React Native 官網: facebook.github.io/react-nativ…
- React Native 中文網: reactnative.cn/
- GitHub 上有個開源庫 awesome-react-native,收集了大量 React Native 的開發資源,值得關注: github.com/jondot/awes…
Weex
- Weex 官網: weex.incubator.apache.org/
- Weex 中文網: weex.incubator.apache.org/cn/
- GitHub 上的 Weex 資源收集項目 awesome-weex: github.com/joggerplus/…
Flutter?
- Flutter 官網: flutter.io/
- Flutter 中文網: flutterchina.club/
- GitHub 上的 awesome-flutter 匯集了大量的 Flutter 資源: github.com/Solido/awes…
- 此外,知識小集團隊在 GitHub 上開了一個 repo,用于收集 Flutter 國內相關開發資源,歡迎關注并和我們一起完善:github.com/awesome-tip…
PWA (Progressive Web App)
- PWA 官網: developers.google.com/web/progres…
- GitHub 上的 awesome-pwa: github.com/hemanth/awe…
- 同樣地,我們也在 GitHub 上開了一個 repo,用于收集 PWA 國內相關的開發資源,歡迎關注并和我們一起完善: github.com/awesome-tip…
?參考:https://juejin.im/post/5b076e3af265da0dce48fe95
小程序
小程序開發本質上還是前端 HTML + CSS + JS 那一套邏輯,它基于 WebView 和微信自己定義的一套 JS/WXML/WXSS/JSON 來開發和渲染頁面。微信官方文檔里提到,小程序運行在三端:iOS、Android 和用于調試的開發者工具,三端的腳本執行環境以及用于渲染非原生組件的環境是各不相同的:
- 在 iOS 上,小程序的 JavaScript 代碼是運行在 JavaScriptCore 中,是由 WKWebView 來渲染的,環境有 iOS 8+;
- 在 Android 上,小程序的 JavaScript 代碼是通過 X5 JSCore 來解析,是由 X5 基于 Mobile Chrome 53/57 內核來渲染的;
- 在 開發工具上, 小程序的 JavaScript 代碼是運行在 nwjs 中,是由 Chrome Webview 來渲染的。
更多細節請查閱微信小程序官網:
- mp.weixin.qq.com/cgi-bin/wx
支付寶后來也上線了自己的小程序平臺:
- open.alipay.com/channel/min…
因為微信或支付寶都可以在 Android 和 iOS 上運行,所以某種意義上,我們也可以把小程序理解為是一種跨平臺開發。
Cordova
- Cardova 官網:?cordova.apache.org/
- Cardova 中文網:?cordova.axuer.com/?
html5
HTML5?是定義?HTML?標準的最新的版本。?該術語通過兩個不同的概念來表現:
- 它是一個新版本的HTML語言,具有新的元素,屬性和行為,
- 它有更大的技術集,允許構建更多樣化和更強大的網站和應用程序。這個集合有時稱為HTML5和它的朋友們,不過大多數時候僅縮寫為一個詞?HTML5。
根據其功能分為幾個組:
- 語義:能夠讓你更恰當地描述你的內容是什么。
- 連通性:能夠讓你和服務器之間通過創新的新技術方法進行通信。
- 離線 & 存儲:能夠讓網頁在客戶端本地存儲數據以及更高效地離線運行。
- 多媒體:使 video 和 audio 成為了在所有 Web 中的一等公民。
- 2D/3D 繪圖 & 效果:提供了一個更加分化范圍的呈現選擇。
- 性能 & 集成:提供了非常顯著的性能優化和更有效的計算機硬件使用。
- 設備訪問 Device Access:能夠處理各種輸入和輸出設備。
- 樣式設計: 讓作者們來創作更加復雜的主題吧!
參考:https://developer.mozilla.org/zh-CN/docs/Web/Guide/HTML/HTML5
組件和模塊的區別
組件化
含義:就是"基礎庫"或者“基礎組件",意思是把代碼重復的部分提煉出一個個組件供給功能使用。
使用:Dialog,各種自定義的UI控件、能在項目或者不同項目重復應用的代碼等等。
目的:復用,解耦。
依賴:組件之間低依賴,比較獨立。
架構定位:縱向分層(位于架構底層,被其他層所依賴)。
總結:組件相當于庫,把一些能在項目里或者不同類型項目中可復用的代碼進行工具性的封裝。
模塊化
含義:就是"業務框架"或者“業務模塊",也可以理解為“框架”,意思是把功能進行劃分,將同一類型的代碼整合在一起,所以模塊的功能相對復雜,但都同屬于一個業務。
使用:按照項目功能需求劃分成不同類型的業務框架(例如:注冊、登錄、外賣、直播.....)
目的:隔離/封裝 (高內聚)。
依賴:模塊之間有依賴的關系,可通過路由器進行模塊之間的耦合問題。
架構定位:橫向分塊(位于架構業務框架層)。
總結:模塊相應于業務邏輯模塊,把同一類型項目里的功能邏輯進行進行需求性的封裝。
參考:https://www.jianshu.com/p/cac0beae8876
前端代碼規范
參考:https://guide.aotu.io/docs/html/code.html
CommonJs與ES6的模塊加載機制
目前階段,通過 Babel 轉碼,CommonJS 模塊的require
命令和 ES6 模塊的import
命令,可以寫在同一個模塊里面,但是最好不要這樣做。因為import
在靜態解析階段執行,所以它是一個模塊之中最早執行的。
ES6模塊
ES6 的模塊自動采用嚴格模式,不管你有沒有在模塊頭部加上"use strict";
。
嚴格模式主要有以下限制。
- 變量必須聲明后再使用
- 函數的參數不能有同名屬性,否則報錯
- 不能使用
with
語句 - 不能對只讀屬性賦值,否則報錯
- 不能使用前綴 0 表示八進制數,否則報錯
- 不能刪除不可刪除的屬性,否則報錯
- 不能刪除變量
delete prop
,會報錯,只能刪除屬性delete global[prop]
eval
不會在它的外層作用域引入變量eval
和arguments
不能被重新賦值arguments
不會自動反映函數參數的變化- 不能使用
arguments.callee
- 不能使用
arguments.caller
- 禁止
this
指向全局對象 - 不能使用
fn.caller
和fn.arguments
獲取函數調用的堆棧 - 增加了保留字(比如
protected
、static
和interface
)?
瀏覽器加載 ES6 模塊,也使用<script>
標簽,但是要加入type="module"
屬性。
<script type="module" src="./foo.js"></script>
上面代碼在網頁中插入一個模塊foo.js
,由于type
屬性設為module
,所以瀏覽器知道這是一個 ES6 模塊。
瀏覽器對于帶有type="module"
的<script>
,都是異步加載,不會造成堵塞瀏覽器,即等到整個頁面渲染完,再執行模塊腳本,等同于打開了<script>
標簽的defer
屬性。
<script type="module" src="./foo.js"></script>
<!-- 等同于 -->
<script type="module" src="./foo.js" defer></script>
如果網頁有多個<script type="module">
,它們會按照在頁面出現的順序依次執行。
對于外部的模塊腳本(上例是foo.js
),有幾點需要注意。
- 代碼是在模塊作用域之中運行,而不是在全局作用域運行。模塊內部的頂層變量,外部不可見。
- 模塊腳本自動采用嚴格模式,不管有沒有聲明
use strict
。 - 模塊之中,可以使用
import
命令加載其他模塊(.js
后綴不可省略,需要提供絕對 URL 或相對 URL),也可以使用export
命令輸出對外接口。 - 模塊之中,頂層的
this
關鍵字返回undefined
,而不是指向window
。也就是說,在模塊頂層使用this
關鍵字,是無意義的。 - 同一個模塊如果加載多次,將只執行一次。
?ES6 模塊與 CommonJS 模塊的差異
討論 Node.js 加載 ES6 模塊之前,必須了解 ES6 模塊與 CommonJS 模塊完全不同。
它們有兩個重大差異。
- CommonJS 模塊輸出的是一個值的拷貝,ES6 模塊輸出的是值的引用。
- CommonJS 模塊是運行時加載,ES6 模塊是編譯時輸出接口。
第二個差異是因為 CommonJS 加載的是一個對象(即module.exports
屬性),該對象只有在腳本運行完才會生成。而 ES6 模塊不是對象,它的對外接口只是一種靜態定義,在代碼靜態解析階段就會生成。
下面重點解釋第一個差異。
CommonJS 模塊輸出的是值的拷貝,也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。
ES6 模塊的運行機制與 CommonJS 不一樣。JS 引擎對腳本靜態分析的時候,遇到模塊加載命令import
,就會生成一個只讀引用。等到腳本真正執行時,再根據這個只讀引用,到被加載的那個模塊里面去取值。換句話說,ES6 的import
有點像 Unix 系統的“符號連接”,原始值變了,import
加載的值也會跟著變。因此,ES6 模塊是動態引用,并且不會緩存值,模塊里面的變量綁定其所在的模塊。
ES6 模塊不會緩存運行結果,而是動態地去被加載的模塊取值,并且變量總是綁定其所在的模塊。
不同的腳本加載相同模塊,得到的都是同一個實例。
Node.js 加載
Node.js 對 ES6 模塊的處理比較麻煩,因為它有自己的 CommonJS 模塊格式,與 ES6 模塊格式是不兼容的。目前的解決方案是,將兩者分開,ES6 模塊和 CommonJS 采用各自的加載方案。從 v13.2 版本開始,Node.js 已經默認打開了 ES6 模塊支持。
Node.js 要求 ES6 模塊采用.mjs
后綴文件名。也就是說,只要腳本文件里面使用import
或者export
命令,那么就必須采用.mjs
后綴名。Node.js 遇到.mjs
文件,就認為它是 ES6 模塊,默認啟用嚴格模式,不必在每個模塊文件頂部指定"use strict"
。
如果不希望將后綴名改成.mjs
,可以在項目的package.json
文件中,指定type
字段為module
。
{"type": "module"
}
一旦設置了以后,該目錄里面的 JS 腳本,就被解釋用 ES6 模塊。
# 解釋成 ES6 模塊
$ node my-app.js
如果這時還要使用 CommonJS 模塊,那么需要將 CommonJS 腳本的后綴名都改成.cjs
。如果沒有type
字段,或者type
字段為commonjs
,則.js
腳本會被解釋成 CommonJS 模塊。
參考:https://es6.ruanyifeng.com/#docs/module-loader
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
前端工程化理解
前端工程化本質上就是將前端開發流程,標準化、規范化、工具化、自動化、簡單化。其主要目的為了提高效率和降低成本,即提高開發過程中的開發效率,減少不必要的重復工作時間。提高編碼、測試、維護階段的生產效率。讓前端開發自成體系
參考:https://juejin.im/post/5e999cecf265da47cd357a24
網站性能監測與優化策略
從性能優化的三大方面工作逐步展開介紹,其中包括網絡傳輸性能、頁面渲染性能以及JS阻塞性能,系統性地帶著讀者們體驗性能優化的實踐流程。
1.網絡傳輸性能優化
Resource Timing API提供了有關每個資產接收時間的詳細信息。請求生命周期的主要階段是:
- 重新導向
- 立即開始
startTime
。 - 如果發生重定向,則也將
redirectStart
開始。 - 如果在此階段結束時發生重定向,
redirectEnd
則將采取重定向。
- 立即開始
- 應用緩存(觀察網頁加載順序,第一個都是document文件先加載,之后查看document里需要哪些文件,如果緩存有則不再重復請求,所以是否可將“應用緩存”放在“處理響應文件”那一步)
- 如果應用緩存滿足了請求,
fetchStart
則將花費一些時間。
- 如果應用緩存滿足了請求,
- 域名解析
domainLookupStart
?時間是在DNS請求開始時開始的。domainLookupEnd
?時間是在DNS請求結束時開始的。
- TCP協議
connectStart
?最初連接服務器時采用。- 如果使用TLS或SSL,
secureConnectionStart
則將在握手開始以確保連接安全時開始。 connectEnd
?與服務器的連接完成后進行。
- 請求
requestStart
?對資源的請求已發送到服務器后,將采用此命令。
- 響應
responseStart
?是服務器最初響應請求的時間。responseEnd
?是請求結束并檢索數據的時間。
- 處理響應文件
domLoading
?開始解析渲染 DOM 樹的時間,此時 Document.readyState 變為 loading,并將拋出 readystatechange 相關事件- domInteractive?表示瀏覽器已經解析好dom樹了,當前網頁DOM結構結束解析、開始加載內嵌資源
- domContentLoadedEventStart?開始觸發DomContentLoaded事件,DOM 解析完成后,網頁內資源加載開始的時間
- domContentLoadedEnd?DomContentLoaded事件結束,DOM 解析完成后,網頁內資源加載完成的時間(如 JS 腳本加載執行完畢)
- domComplete?DOM 樹解析渲染完成,且資源也準備就緒的時間(包括圖像、腳本文件、CSS 文件等),Document.readyState 變為 complete,并將拋出 readystatechange 相關事件
- loadEventStart?load 事件發送給文檔,也即 load 回調函數開始執行的時間
- loadEventEnd?load 事件的回調函數執行完畢的時間
參考:http://www.alloyteam.com/2015/09/explore-performance/
這是navigation timing監測指標圖,從圖中我們可以看出,瀏覽器在得到用戶請求之后,經歷了下面這些階段:重定向→拉取緩存→DNS查詢→建立TCP鏈接→發起請求→接收響應→處理HTML元素→元素加載完成。
例如發送一個請求,會經過如下:
排隊
排隊的請求表明:
- 該請求被渲染引擎推遲了,因為它被認為比關鍵資源(例如腳本/樣式)的優先級低。這通常發生在圖像上。
- 該請求被暫停以等待將要釋放的不可用的TCP套接字。
- 由于瀏覽器在HTTP 1上每個源僅允許六個TCP連接,因此該請求被暫停。
- 花在制作磁盤緩存條目上的時間(通常非常快。)
?堵轉/堵轉
請求等待發送之前花費的時間。它可能正在等待隊列中描述的任何原因。此外,此時間包括在代理協商中花費的任何時間。
?代理協商
與代理服務器連接進行協商所花費的時間。
DNS查詢
執行DNS查找所花費的時間。頁面上的每個新域都需要完整的往返來進行DNS查找。
?初始連接/連接
建立連接所花費的時間,包括TCP握手/重試和協商SSL。
?SSL協議
完成SSL握手所花費的時間。
?請求發送/發送
發出網絡請求所花費的時間。通常為一毫秒的時間。
等待中(TTFB)
等待初始響應所花費的時間,也稱為第一個字節的時間。除了等待服務器傳遞響應所花費的時間之外,該時間還捕獲了到服務器的往返延遲。
?內容下載/下載
接收響應數據所花費的時間。
參考:https://developers.google.cn/web/tools/chrome-devtools/network/understanding-resource-timing
頁面加載時,大致可以分為以下幾個步驟:
- 開始解析HTML文檔結構
- 加載外部樣式表及JavaScript腳本
- 解析執行JavaScript腳本
- DOM樹渲染完成
- 加載未完成的外部資源(如 圖片)
- ? ? ? ?頁面加載成功
1.1.瀏覽器緩存
我們都知道,瀏覽器在向服務器發起請求前,會先查詢本地是否有相同的文件,如果有,就會直接拉取本地緩存,這和我們在后臺部屬的Redis、Memcache類似,都是起到了中間緩沖的作用,我們先看看瀏覽器處理緩存的策略:
瀏覽器默認的緩存是放在內存內的,但我們知道,內存里的緩存會因為進程的結束或者說瀏覽器的關閉而被清除,而存在硬盤里的緩存才能夠被長期保留下去。很多時候,我們在network面板中各請求的size項里,會看到兩種不同的狀態:from memory cache
和 from disk cache
,前者指緩存來自內存,后者指緩存來自硬盤。而控制緩存存放位置的,不是別人,就是我們在服務器上設置的Etag字段。在瀏覽器接收到服務器響應后,會檢測響應頭部(Header),如果有Etag字段,那么瀏覽器就會將本次緩存寫入硬盤中。
之所以拉取緩存會出現200、304兩種不同的狀態碼,取決于瀏覽器是否有向服務器發起驗證請求。 只有向服務器發起驗證請求并確認緩存未被更新,才會返回304狀態碼。
【!!!特別注意!!!】在我們配置緩存時一定要切記,瀏覽器在處理用戶請求時,如果命中強緩存,瀏覽器會直接拉取本地緩存,不會與服務器發生任何通信,也就是說,如果我們在服務器端更新了文件,并不會被瀏覽器得知,就無法替換失效的緩存。所以我們在構建階段,需要為我們的靜態資源添加md5 hash后綴,避免資源更新而引起的前后端文件無法同步的問題。
1.2.資源打包壓縮
我們之前所作的瀏覽器緩存工作,只有在用戶第二次訪問我們的頁面才能起到效果,如果要在用戶首次打開頁面就實現優良的性能,必須對資源進行優化。我們常將網絡性能優化措施歸結為三大方面:減少請求數、減小請求資源體積、提升網絡傳輸速率。現在,讓我們逐個擊破:
結合前端工程化思想,我們在對上線文件進行自動化打包編譯時,通常都需要打包工具的協助,這里我推薦webpack,我通常都使用Gulp和Grunt來編譯node
在對webpack進行上線配置時,我們要特別注意以下幾點:
①JS壓縮:(這點應該算是耳熟能詳了,就不多介紹了)
②HTML壓縮
③提取公共資源
④提取css并壓縮
⑤使用webpack3的新特性:ModuleConcatenationPlugin
⑥在服務器上開啟Gzip傳輸壓縮,它能將我們的文本類文件體積壓縮至原先的四分之一,效果立竿見影
如果你在網站請求的響應頭里看到這樣的字段,那么就說明咱們的Gzip壓縮配置成功啦:
?
【!!!特別注意!!!】不要對圖片文件進行Gzip壓縮!不要對圖片文件進行Gzip壓縮!不要對圖片文件進行Gzip壓縮!我只會告訴你效果適得其反,至于具體原因,還得考慮服務器壓縮過程中的CPU占用還有壓縮率等指標,對圖片進行壓縮不但會占用后臺大量資源,壓縮效果其實并不可觀,可以說是“弊大于利”,所以請在gzip_types
把圖片的相關項去掉。針對圖片的相關處理,我們接下來會更加具體地介紹。
1.3.圖片資源優化
剛剛我們介紹了資源打包壓縮,只是停留在了代碼層面,而在我們實際開發中,真正占用了大量網絡傳輸資源的,并不是這些文件,而是圖片,如果你對圖片進行了優化工作,你能立刻看見明顯的效果。
1.3.1.不要在HTML里縮放圖像
需要用多大的圖片時,就在服務器上準備好多大的圖片,盡量固定圖片尺寸。
1.3.2.使用雪碧圖(CSS Sprite)
這里給大家推薦一個自動化生成雪碧圖的工具:www.toptal.com/developers/…?
1.3.3.使用字體圖標(iconfont)
阿里矢量圖標庫(網址:www.iconfont.cn/?),圖片能做的很多事情,矢量圖都能作,而且它只是往HTML里插入字符和CSS樣式而已,和圖片請求比起來,在網絡傳輸資源的占用上它們完全不在一個數量級,如果你的項目里有大量的小圖標,就用矢量圖吧。
1.3.4.使用WebP
WebP格式,是谷歌公司開發的一種旨在加快圖片加載速度的圖片格式。圖片壓縮體積大約只有JPEG的2/3,并能節省大量的服務器帶寬資源和數據空間。Facebook、Ebay等知名網站已經開始測試并使用WebP格式。
我們可以使用官網提供的Linux命令行工具對項目中的圖片進行WebP編碼,也可以使用我們的線上服務,這里我推薦叉拍云(網址:www.upyun.com/webp )。但是在實際的上線工作中,我們還是得編寫Shell腳本使用命令行工具進行批量編碼,不過測試階段我們用線上服務就足夠了,方便快捷。
1.4.網絡傳輸性能檢測工具——Page Speed
谷歌監測網絡傳輸性能的插件——PageSpeed Insights for Chrome
Page Speed最人性化的地方,便是它會對測試網站的性能瓶頸提出完整的建議,我們可以根據它的提示進行優化工作。這里我的網站已經優化到最好指標了(??????)??,Page Speed Score表示你的性能測試得分,100/100表示已經沒有需要優化的地方。
1.5.使用CDN
再好的性能優化實例,也必須在CDN的支撐下才能到達極致。
當然,憑著我們單個人的資金實力(除非你是王思聰)是必定搭建不起來CDN的,不過我們可以使用各大企業提供的服務,諸如騰訊云等,配置也十分簡單,這里就請大家自行去推敲啦。
其實我們的CDN域名一般是和我們的網站主域名不同的,大家可以看看淘寶、騰訊的官方網站,看看他們存放靜態資源的CDN域名,都是和主域名不一樣的。為什么要這么做?主要有兩個原因:[內容摘選自:bbs.aliyun.com/simple/t116… ]
①便于CDN業務獨立,能夠獨立配置緩存。為了降低web壓力,CDN系統會遵循Cache-Control和Expires HTTP頭標準對改請求返回的內容進行緩存,便于后面的請求不在回源,起到加速功能。而傳統CDN(Web與CDN共用域名)的方式,需要對不同類型的文件設置相應的Cache規則或者遵循后端的HTTP頭,但這樣難以發揮CDN的最大優勢,因為動態請求回源的概率非常之大,如果訪客與源站的線路并不慢,通過CDN的請求未必快于直接請求源站的。 大型網站為了提升web性能到極致,通常緩存頭設置比較大,像谷歌JS設置一年緩存,百度首頁logo設置十年緩存,如果將靜態元素抽取出來,就可以很方便的對所有靜態元素部署規則,而不用考慮動態請求。減少規則的條數可以提升CDN的效率。
②拋開無用cookie,減小帶寬占用。我們都知道HTTP協議每次發送請求都會自動帶上該域名及父級域名下的cookie,但對于CSS,JS還有圖片資源,這些cookie是沒用的,反而會浪費訪客帶寬和服務器入帶寬。而我們的主站,為了保持會話或者做其他緩存,都會存放著大量的cookie,所以如果將CDN與主站域名分離,就能解決這一問題。
不過這樣一來,新的問題就出現了:CDN域名與主站域名不同,DNS解析CDN域名還需要花費額外的時間,增加網絡延遲。不過這難不住我們偉大的程序員前輩,DNS Prefetch閃亮登場。
如果大家翻看大型網站的HTML源代碼,都會在頭部發現這樣的link鏈接:(這里以淘寶首頁為例)
這就是DNS Prefetch。DNS Prefetch是一種DNS預解析技術,當我們瀏覽網頁時,瀏覽器會在加載網頁時對網頁中的域名進行預解析并緩存,這樣在瀏覽器加載網頁中的鏈接時,就無需進行DNS解析,減少用戶的等待時間,提高用戶體驗。DNS Prefetch現已被主流瀏覽器支持,大多數瀏覽器針對DNS解析都進行了優化,典型的一次DNS解析會耗費20~120ms,減少DNS解析時間和次數是個很好的優化措施。
大公司的靜態資源優化方案,基本上要實現這么幾個東西:
- 配置超長時間的本地緩存 —— 節省帶寬,提高性能(強制瀏覽器使用本地緩存(cache-control/expires),不要和服務器通信。)
- 采用內容摘要作為緩存更新依據 —— 精確的緩存控制
- 靜態資源CDN部署 —— 優化網絡請求(靜態資源和動態網頁分集群部署,靜態資源會被部署到CDN節點上)
- 更資源發布路徑實現非覆蓋式發布 —— 平滑升級
參考:https://www.zhihu.com/question/20790576/answer/32602154
2.頁面渲染性能優化
2.1.瀏覽器渲染過程(Webkit)
?
瀏覽器渲染的流程如下:
- HTML被HTML解析器解析成DOM 樹
- css則被css解析器解析成CSSOM 樹
- 結合DOM樹和CSSOM樹,生成一棵渲染樹(Render Tree)
- 生成布局(flow),即將所有渲染樹的所有節點進行平面合成
- 將布局繪制(paint)在屏幕上
第四步和第五步是最耗時的部分,這兩步合起來,就是我們通常所說的渲染。
網頁生成的時候,至少會渲染一次。
在用戶訪問的過程中,還會不斷重新渲染
重新渲染需要重復之前的第四步(重新生成布局)+第五步(重新繪制)或者只有第五個步(重新繪制)。
注意:
-
css加載不會阻塞DOM樹的解析
-
css加載會阻塞DOM樹的渲染,由于Render Tree是依賴于DOM Tree和CSSOM Tree的,所以他必須等待到CSSOM Tree構建完成,也就是CSS資源加載完成(或者CSS資源加載失敗)后,才能開始渲染。因此,CSS加載是會阻塞Dom的渲染的。
-
css加載會阻塞后面js語句的執行
?link和@import的區別
結論
就結論而言,強烈建議使用link
標簽,慎用@import
方式。
這樣可以避免考慮@import
的語法規則和注意事項,避免產生資源文件下載順序混亂和http請求過多的煩惱。
區別
1.從屬關系區別@import
是 CSS 提供的語法規則,只有導入樣式表的作用;link
是HTML提供的標簽,不僅可以加載 CSS 文件,還可以定義 RSS、rel 連接屬性等。
2.加載順序區別
加載頁面時,link
標簽引入的 CSS 被同時加載;@import
引入的 CSS 將在頁面加載完畢后被加載。
3.兼容性區別@import
是 CSS2.1 才有的語法,故只可在 IE5+ 才能識別;link
標簽作為 HTML 元素,不存在兼容性問題。
4.DOM可控性區別
可以通過 JS 操作 DOM ,插入link
標簽來改變樣式;由于 DOM 方法是基于文檔的,無法使用@import
的方式插入樣式。
5.權重區別(該項有爭議,下文將詳解)link
引入的樣式權重大于@import
引入的樣式。
css優化建議:
1. 內聯首屏關鍵CSS(Critical CSS)
Github上有一個項目Critical CSS4,可以將屬于首屏的關鍵樣式提取出來,大家可以看一下該項目,結合自己的構建工具進行使用。當然為了保證正確,大家最好再親自確認下提取出的內容是否有缺失。
2. 文件壓縮
相信大家都早已習慣對CSS進行壓縮,現在的構建工具,如webpack、gulp/grunt、rollup等也都支持CSS壓縮功能。壓縮后的文件能夠明顯減小,可以大大降低了瀏覽器的加載時間。
3. 去除無用的css
雖然文件壓縮能夠降低文件大小。但CSS文件壓縮通常只會去除無用的空格,這樣就限制了CSS文件的壓縮比例。那是否還有其他手段來精簡CSS呢?答案顯然是肯定的,如果壓縮后的文件仍然超出了預期的大小,我們可以試著找到并刪除代碼中無用的CSS。
一般情況下,會存在這兩種無用的CSS代碼:一種是不同元素或者其他情況下的重復代碼,一種是整個頁面內沒有生效的CSS代碼。
當然,如果手動刪除這些無用CSS是很低效的。我們可以借助Uncss7庫來進行。Uncss可以用來移除樣式表中的無用CSS,并且支持多文件和JavaScript注入的CSS。
4. 有選擇地使用選擇器
大多數朋友應該都知道CSS選擇器的匹配是從右向左進行的,這一策略導致了不同種類的選擇器之間的性能也存在差異。相比于#markdown-content-h3
,顯然使用#markdown .content h3
時,瀏覽器生成渲染樹(render-tree)所要花費的時間更多。
我們在使用選擇器時,只需要記住以下幾點,其他的可以全憑喜好。
- 保持簡單,不要使用嵌套過多過于復雜的選擇器。
- 通配符和屬性選擇器效率最低,需要匹配的元素最多,盡量避免使用。
- 不要使用類選擇器和ID選擇器修飾元素標簽,如
h3#markdown-content
,這樣多此一舉,還會降低效率。 - 不要為了追求速度而放棄可讀性與可維護性。
5.?減少使用昂貴的屬性
在瀏覽器繪制屏幕時,所有需要瀏覽器進行操作或計算的屬性相對而言都需要花費更大的代價。當頁面發生重繪時,它們會降低瀏覽器的渲染性能。所以在編寫CSS時,我們應該盡量減少使用昂貴屬性,如box-shadow
/border-radius
/filter
/透明度/:nth-child
等。
6.?不要使用@import
參考:?https://juejin.im/post/5b6133a351882519d346853f
?
?
瀏覽器的解釋器,是包括在渲染引擎內的,我們常說的Chrome(現在使用的是Blink引擎)和Safari使用的Webkit引擎,Firefox使用的Gecko引擎,指的就是渲染引擎。而在渲染引擎內,還包括著我們的HTML解釋器(渲染時用于構造DOM樹)、CSS解釋器(渲染時用于合成CSS規則)還有我們的JS解釋器。不過后來,由于JS的使用越來越重要,工作越來越繁雜,所以JS解釋器也漸漸獨立出來,成為了單獨的JS引擎,就像眾所周知的V8引擎,我們經常接觸的Node.js也是用的它。
2.2.DOM渲染層與GPU硬件加速
頁面的真實樣子就是這樣,是由多個DOM元素渲染層(Layers)組成的,實際上一個頁面在構建完Render Tree之后,是經歷了這樣的流程才最終呈現在我們面前的:
①瀏覽器會先獲取DOM樹并依據樣式將其分割成多個獨立的渲染層
②CPU將每個層繪制進繪圖中
③將位圖作為紋理上傳至GPU(顯卡)繪制
④GPU將所有的渲染層緩存(如果下次上傳的渲染層沒有發生變化,GPU就不需要對其進行重繪)并復合多個渲染層最終形成我們的圖像
從上面的步驟我們可以知道,布局是由CPU處理的,而繪制則是由GPU完成的。
把那些一直發生大量重排重繪的元素提取出來,單獨觸發一個渲染層,那樣這個元素不就不會“連累”其他元素一塊重繪了對吧。
那么問題來了,什么情況下會觸發渲染層呢?大家只要記住:
Video元素、WebGL、Canvas、CSS3 3D、CSS濾鏡、z-index大于某個相鄰節點的元素都會觸發新的Layer,其實我們最常用的方法,就是給某個元素加上下面的樣式:
transform: translateZ(0);
backface-visibility: hidden;
這樣就可以觸發渲染層啦 。
我們把容易觸發重排重繪的元素單獨觸發渲染層,讓它與那些“靜態”元素隔離,讓GPU分擔更多的渲染工作,我們通常把這樣的措施成為硬件加速,或者是GPU加速。大家之前肯定聽過這個說法,現在完全清楚它的原理了吧。
2.3.重排與重繪
①重排(reflow):渲染層內的元素布局發生修改,都會導致頁面重新排列,比如窗口的尺寸發生變化、刪除或添加DOM元素,修改了影響元素盒子大小的CSS屬性(諸如:width、height、padding)。
②重繪(repaint):繪制,即渲染上色,所有對元素的視覺表現屬性的修改,都會引發重繪。
不論是重排還是重繪,都會阻塞瀏覽器。要提高網頁性能,就要降低重排和重繪的頻率和成本,近可能少地觸發重新渲染。正如我們在2.3中提到的,重排是由CPU處理的,而重繪是由GPU處理的,CPU的處理效率遠不及GPU,并且重排一定會引發重繪,而重繪不一定會引發重排。所以在性能優化工作中,我們更應當著重減少重排的發生。
常見引起重排屬性和方法
任何會改變元素幾何信息(元素的位置和尺寸大小)的操作,都會觸發重排,下面列一些栗子:
- 添加或者刪除可見的DOM元素;
- 元素尺寸改變——邊距、填充、邊框、寬度和高度
- 內容變化,比如用戶在input框中輸入文字
- 瀏覽器窗口尺寸改變——resize事件發生時
- 計算 offsetWidth 和 offsetHeight 屬性
- 設置 style 屬性的值
瀏覽器的渲染隊列:
思考以下代碼將會觸發幾次渲染?
div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
根據我們上文的定義,這段代碼理論上會觸發4次重排+重繪,因為每一次都改變了元素的幾何屬性,實際上最后只觸發了一次重排,這都得益于瀏覽器的渲染隊列機制:
當我們修改了元素的幾何屬性,導致瀏覽器觸發重排或重繪時。它會把該操作放進渲染隊列,等到隊列中的操作到了一定的數量或者到了一定的時間間隔時,瀏覽器就會批量執行這些操作。
強制刷新隊列:
div.style.left = '10px';
console.log(div.offsetLeft);
div.style.top = '10px';
console.log(div.offsetTop);
div.style.width = '20px';
console.log(div.offsetWidth);
div.style.height = '20px';
console.log(div.offsetHeight);
這段代碼會觸發4次重排+重繪,因為在console
中你請求的這幾個樣式信息,無論何時瀏覽器都會立即執行渲染隊列的任務,即使該值與你操作中修改的值沒關聯。
因為隊列中,可能會有影響到這些值的操作,為了給我們最精確的值,瀏覽器會立即重排+重繪。
強制刷新隊列的style樣式請求:
- offsetTop, offsetLeft, offsetWidth, offsetHeight
- scrollTop, scrollLeft, scrollWidth, scrollHeight
- clientTop, clientLeft, clientWidth, clientHeight
- getComputedStyle(), 或者 IE的 currentStyle
我們在開發中,應該謹慎的使用這些style請求,注意上下文關系,避免一行代碼一個重排,這對性能是個巨大的消耗
重排優化建議
就像上文提到的我們要盡可能的減少重排次數、重排范圍,這樣說很泛,下面是一些行之有效的建議,大家可以參考一下。
1. 分離讀寫操作
div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
console.log(div.offsetLeft);
console.log(div.offsetTop);
console.log(div.offsetWidth);
console.log(div.offsetHeight);
還是上面觸發4次重排+重繪的代碼,這次只觸發了一次重排:
在第一個console
的時候,瀏覽器把之前上面四個寫操作的渲染隊列都給清空了。剩下的console,因為渲染隊列本來就是空的,所以并沒有觸發重排,僅僅拿值而已。最最最客觀的解決方案,就是不用JS去操作元素樣式,這也是我最推薦的。
2. 樣式集中改變
div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
雖然現在大部分瀏覽器有渲染隊列優化,不排除有些瀏覽器以及老版本的瀏覽器效率仍然低下:
建議通過改變class或者csstext屬性集中改變樣式
// bad
var left = 10;
var top = 10;
el.style.left = left + "px";
el.style.top = top + "px";
// good
el.className += " theclassname";
// good
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";
3. 緩存布局信息
// bad 強制刷新 觸發兩次重排
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';// good 緩存布局信息 相當于讀寫分離
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';
4. 離線改變dom
-
隱藏要操作的dom
在要操作dom之前,通過display隱藏dom,當操作完成之后,才將元素的display屬性為可見,因為不可見的元素不會觸發重排和重繪。
dom.display = 'none' // 修改dom樣式 dom.display = 'block'
-
通過使用DocumentFragment創建一個
dom
碎片,在它上面批量操作dom,操作完成之后,再添加到文檔中,這樣只會觸發一次重排。 -
復制節點,在副本上工作,然后替換它!
5. position屬性為absolute或fixed
position屬性為absolute或fixed的元素,重排開銷比較小,不用考慮它對其他元素的影響
6. 優化動畫
-
可以把動畫效果應用到position屬性為absolute或fixed的元素上,這樣對其他元素影響較小
動畫效果還應犧牲一些平滑,來換取速度,這中間的度自己衡量:
比如實現一個動畫,以1個像素為單位移動這樣最平滑,但是reflow就會過于頻繁,大量消耗CPU資源,如果以3個像素為單位移動則會好很多。
-
啟用GPPU加速
此部分來自優化CSS重排重繪與瀏覽器性能
GPU(圖像加速器):
GPU 硬件加速是指應用 GPU 的圖形性能對瀏覽器中的一些圖形操作交給 GPU 來完成,因為 GPU 是專門為處理圖形而設計,所以它在速度和能耗上更有效率。
GPU 加速通常包括以下幾個部分:Canvas2D,布局合成, CSS3轉換(transitions),CSS3 3D變換(transforms),WebGL和視頻(video)。
(這項策略需要慎用,得著重考量以犧牲GPU占用率為代價能否換來可期的性能優化,畢竟頁面中存在太多的渲染層對于GPU而言也是一種不必要的壓力,通常情況下,我們會對動畫元素采取硬件加速。)
/** 根據上面的結論* 將 2d transform 換成 3d* 就可以強制開啟 GPU 加速* 提高動畫性能*/ div {transform: translate3d(10px, 10px, 0); }
7 .?visibility: hidden?
減小重繪的壓力
將沒用的元素設為不可見:visibility: hidden
,這樣可以減小重繪的壓力,必要的時候再將元素顯示。
8.壓縮DOM的深度
一個渲染層內不要有過深的子元素,少用DOM完成頁面樣式,多使用偽元素或者box-shadow取代。
9.圖片在渲染前指定大小
因為img元素是內聯元素,所以在加載圖片后會改變寬高,嚴重的情況會導致整個頁面重排,所以最好在渲染前就指定其大小,或者讓其脫離文檔流。
參考:https://juejin.im/post/5c15f797f265da61141c7f86
3.JS阻塞性能
腳本帶來的問題就是他會阻塞頁面的平行下載,還會提高進程的CPU占用率。更有甚者,現在node.js已經在前端開發中普及,稍有不慎,我們引發了內存泄漏,或者在代碼中誤寫了死循環,會直接造成我們的服務器崩潰。
在編程的過程中,如果我們使用了閉包后未將相關資源加以釋放,或者引用了外鏈后未將其置空(比如給某DOM元素綁定了事件回調,后來卻remove了該元素),都會造成內存泄漏的情況發生,進而大量占用用戶的CPU,造成卡頓或死機。我們可以使用chrome提供的JavaScript Profile版塊,開啟方式同Layers等版塊
其實瀏覽器強大的內存回收機制在大多數時候避免了這一情況的發生,即便用戶發生了死機,他只要結束相關進程(或關閉瀏覽器)就可以解決這一問題,但我們要知道,同樣的情況還會發生在我們的服務器端,也就是我們的node中,嚴重的情況,會直接造成我們的服務器宕機,網站崩潰。所以更多時候,我們都使用JavaScript Profile版塊來對我們的node服務進行壓力測試,搭配node-inspector
插件,我們能更有效地檢測JS執行時各函數的CPU占用率,針對性地進行優化。
(PS:所以沒修煉到一定水平,千萬別在服務端使用閉包,一個是真沒啥用,我們會有更多優良的解決辦法,二是真的很容易內存泄漏,造成的后果是你無法預期的)
js方面的性能優化小建議
①不要使用 with() 語句
和函數類似 ,with語句會創建自己的作用域,因此會增加其中執行的代碼的作用域鏈的長度,由于額外的作用域鏈的查找,在with語句中執行的代碼肯定會比外面執行的代碼要慢,在能不使用with語句的時候盡量不要使用with語句。
②避免全局查找
在一個函數中會用到全局對象存儲為局部變量來減少全局查找,因為訪問局部變量的速度要比訪問全局變量的速度更快些
function search() {//當我要使用當前頁面地址和主機域名alert(window.location.href + window.location.host);}//最好的方式是如下這樣 先用一個簡單變量保存起來function search() {var location = window.location;alert(location.href + location.host);}
③通過模板元素clone,替代createElement
如果文檔中存在現成的樣板節點,應該是用cloneNode()方法,因為使用createElement()方法之后,你需要設置多次元素的屬性,使用cloneNode()則可以減少屬性的設置次數——同樣如果需要創建很多元素,應該先準備一個樣板節點。
④在循環時將控制條件和控制變量合并起來
提到性能,在循環中需要避免的工作一直是個熱門話題,因為循環會被重復執行很多次。所以如果有性能優化的需求,先對循環開刀有可能會獲得最明顯的性能提升。
一種優化循環的方法是在定義循環的時候,將控制條件和控制變量合并起來,下面是一個沒有將他們合并起來的例子:
for ( var x = 0; x < 10; x++ ) {
};
當我們要添加什么東西到這個循環之前,我們發現有幾個操作在每次迭代都會出現。JavaScript引擎需要:
#1:檢查 x 是否存在
#2:檢查 x 是否小于 0 <span style="color: #888888;">(這里可能有筆誤)</span>
#3:使 x 增加 1
然而如果你只是迭代元素中的一些元素,那么你可以使用while循環進行輪轉來替代上面這種操作:
var x = 9;
do { } while( x-- );
⑤注意NodeList
最小化訪問NodeList的次數可以極大的改進腳本的性能
var images = document.getElementsByTagName('img');for (var i = 0, len = images.length; i < len; i++) {}
編寫JavaScript的時候一定要知道何時返回NodeList對象,這樣可以最小化對它們的訪問
- 進行了對getElementsByTagName()的調用
- 獲取了元素的childNodes屬性
- 獲取了元素的attributes屬性
- 訪問了特殊的集合,如document.forms、document.images等等
要了解了當使用NodeList對象時,合理使用會極大的提升代碼執行速度
⑥避免與null進行比較
由于JavaScript是弱類型的,所以它不會做任何的自動類型檢查,所以如果看到與null進行比較的代碼,嘗試使用以下技術替換:
- 如果值應為一個引用類型,使用instanceof操作符檢查其構造函數
- 如果值應為一個基本類型,作用typeof檢查其類型
- 如果是希望對象包含某個特定的方法名,則使用typeof操作符確保指定名字的方法存在于對象上
- Object.prototype.toString方法檢測對象類型
可以通過?toString()
?來獲取每個對象的類型。為了每個對象都能通過?Object.prototype.toString()
?來檢測,需要以?Function.prototype.call()
?或者?Function.prototype.apply()
?的形式來調用,傳遞要檢查的對象作為第一個參數,稱為?thisArg
。?
var toString = Object.prototype.toString;toString.call(new Date); // [object Date]
toString.call(new String); // [object String]
toString.call(Math); // [object Math]
toString.call('hahah'); // [object String]//Since JavaScript 1.8.5
toString.call(undefined); // [object Undefined]
toString.call(null); // [object Null]
⑦循環引用
⑧釋放javascript對象
在rich應用中,隨著實例化對象數量的增加,內存消耗會越來越大。所以應當及時釋放對對象的引用,讓GC能夠回收這些內存控件。
對象:obj = null
對象屬性:delete obj.myproperty
數組item:使用數組的splice方法釋放數組中不用的item
⑨switch語句相對if較快
通過將case語句按照最可能到最不可能的順序進行組織
?擴展:你不知道的Node.js性能優化、Redis【入門】就這一篇!、Redis的優勢和特點
前端性能優化 24 條建議(2020)
4.【拓展】負載均衡
我們都知道node的核心是事件驅動,通過Event Loop去異步處理用戶請求,相比于傳統的后端服務,它們都是將用戶的每個請求分配一個進程進行處理,推薦大家去看這樣一篇博文:mp.weixin.qq.com/s?__biz=MzA… 。特別生動地講解了事件驅動的運行機制,通俗易懂。事件驅動的最大優勢是什么?就是在高并發IO時,不會造成堵塞,對于直播類網站,這點是至關重要的,我們有成功的先例——快手,快手強大的IO高并發究其本質一定能追溯到node。
其實現在的企業級網站,都會搭建一層node作為中間層。大概的網站框架如圖所示:
?
?
4.2.pm2實現Node.js“多進程”
我們都知道node的優劣,這里分享一份鏈接,找了挺久寫的還算詳細:www.zhihu.com/question/19… 。其實很多都是老套路,那些說node不行的都是指著node是單進程這一個軟肋開撕,告訴你,我們有解決方案了——pm2。這是它的官網:pm2.keymetrics.io/ 。它是一款node.js進程管理器,具體的功能,就是能在你的計算機里的每一個內核都啟動一個node.js服務,也就是說如果你的電腦或者服務器是多核處理器(現在也少見單核了吧),它就能啟動多個node.js服務,并且它能夠自動控制負載均衡,會自動將用戶的請求分發至壓力小的服務進程上處理。聽起來這東西簡直就是神器啊!而且它的功能遠遠不止這些,這里我就不作過多介紹了,大家知道我們在上線的時候需要用到它就行了,安裝的方法也很簡單,直接用npm下到全局就可以了$ npm i pm2 -g
具體的使用方法還有相關特性可以參照官網。
下面是pm2啟動后的效果圖:
?
參考:?https://www.cnblogs.com/chyingp/p/pm2-documentation.html?
4.3.nginx搭建反向代理
在開始搭建工作之前,首先得知道什么是反向代理。可能大家對這個名詞比較陌生,先上一張圖:
?
所謂代理就是我們通常所說的中介,網站的反向代理就是指那臺介于用戶和我們真實服務器之間的服務器(說的我都拗口了),它的作用便是能夠將用戶的請求分配到壓力較小的服務器上,其機制是輪詢。聽完這句話是不是感覺很耳熟,沒錯,在我介紹pm2的時候也說過同樣的話,反向代理起到的作用同pm2一樣也是實現負載均衡,你現在應該也明白了兩者之間的差異,反向代理是對服務器實現負載均衡,而pm2是對進程實現負載均衡。大家如果想深入了解反向代理的相關知識,我推薦知乎的一個貼子:www.zhihu.com/question/24… 。
參考:https://juejin.im/post/5b0b7d74518825158e173a0c#heading-18
?
?
?
?
RESTful架構
即Representational State Transfer的縮寫。我對這個詞組的翻譯是"表現層狀態轉化"。
要理解RESTful架構,最好的方法就是去理解Representational State Transfer這個詞組到底是什么意思,它的每一個詞代表了什么涵義。如果你把這個名稱搞懂了,也就不難體會REST是一種什么樣的設計。
資源(Resources)
REST的名稱"表現層狀態轉化"中,省略了主語。"表現層"其實指的是"資源"(Resources)的"表現層"。
所謂"資源",就是網絡上的一個實體,或者說是網絡上的一個具體信息。它可以是一段文本、一張圖片、一首歌曲、一種服務,總之就是一個具體的實在。你可以用一個URI(統一資源定位符)指向它,每種資源對應一個特定的URI。要獲取這個資源,訪問它的URI就可以,因此URI就成了每一個資源的地址或獨一無二的識別符。
所謂"上網",就是與互聯網上一系列的"資源"互動,調用它的URI。
表現層(Representation)
"資源"是一種信息實體,它可以有多種外在表現形式。我們把"資源"具體呈現出來的形式,叫做它的"表現層"(Representation)。
比如,文本可以用txt格式表現,也可以用HTML格式、XML格式、JSON格式表現,甚至可以采用二進制格式;圖片可以用JPG格式表現,也可以用PNG格式表現。
URI只代表資源的實體的位置,不代表它的形式。嚴格地說,有些網址最后的".html"后綴名是不必要的,因為這個后綴名表示格式,屬于"表現層"范疇,而URI應該只代表"資源"的位置。它的具體表現形式,應該在HTTP請求的頭信息中用Accept和Content-Type字段指定,這兩個字段才是對"表現層"的描述。
狀態轉化(State Transfer)
訪問一個網站,就代表了客戶端和服務器的一個互動過程。在這個過程中,勢必涉及到數據和狀態的變化。
互聯網通信協議HTTP協議,是一個無狀態協議。這意味著,所有的狀態都保存在服務器端。因此,如果客戶端想要操作服務器,必須通過某種手段,讓服務器端發生"狀態轉化"(State Transfer)。而這種轉化是建立在表現層之上的,所以就是"表現層狀態轉化"。
客戶端用到的手段,只能是HTTP協議。具體來說,就是HTTP協議里面,四個表示操作方式的動詞:GET、POST、PUT、DELETE。它們分別對應四種基本操作:GET用來獲取資源,POST用來新建資源(也可以用于更新資源),PUT用來更新資源,DELETE用來刪除資源。
綜述
綜合上面的解釋,我們總結一下什么是RESTful架構:
(1)每一個URI代表一種資源;
(2)客戶端和服務器之間,傳遞這種資源的所需的外在表現形式類型;
(3)客戶端通過四個HTTP動詞,對服務器端資源進行操作,實現"表現層狀態轉化"。
誤區
RESTful架構有一些典型的設計誤區。
最常見的一種設計錯誤,就是URI包含動詞。因為"資源"表示一種實體,所以應該是名詞,URI不應該有動詞,動詞應該放在HTTP協議中。
舉例來說,某個URI是/posts/show/1,其中show是動詞,這個URI就設計錯了,正確的寫法應該是/posts/1,然后用GET方法表示show。
如果某些動作是HTTP動詞表示不了的,你就應該把動作做成一種資源。比如網上匯款,從賬戶1向賬戶2匯款500元,錯誤的URI是:
POST /accounts/1/transfer/500/to/2
正確的寫法是把動詞transfer改成名詞transaction,資源不能是動詞,但是可以是一種服務:
POST /transaction HTTP/1.1Host: 127.0.0.1from=1&to=2&amount=500.00
另一個設計誤區,就是在URI中加入版本號:
http://www.example.com/app/1.0/foohttp://www.example.com/app/1.1/foohttp://www.example.com/app/2.0/foo
因為不同的版本,可以理解成同一種資源的不同表現形式,所以應該采用同一個URI。版本號可以在HTTP請求頭信息的Accept字段中進行區分(參見Versioning REST Services):
Accept: vnd.example-com.foo+json; version=1.0Accept: vnd.example-com.foo+json; version=1.1Accept: vnd.example-com.foo+json; version=2.0
參考:http://www.ruanyifeng.com/blog/2011/09/restful.html
css優先級
下面列表中,選擇器類型的優先級是遞增的:
- 類型選擇器(例如,
h1
)和偽元素(例如,::before
) - 類選擇器?(例如,
.example
),屬性選擇器(例如,[type="radio"]
)和偽類(例如,:hover
) - ID 選擇器(例如,
#example
)。 - 給元素添加的內聯樣式?(例如,
style="font-weight:bold"
) 總會覆蓋外部樣式表的任何樣式 ,因此可看作是具有最高的優先級。
通配選擇符(universal selector)(*
)關系選擇符(combinators)(+
,?>
,?~
,?'?
',?||
)和?否定偽類(negation pseudo-class)(:not()
)對優先級沒有影響。(但是,在?:not()
?內部聲明的選擇器會影響優先級)。
您可以訪問?https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Cascade_and_inheritance#Specificity_2? 或者?https://specifishity.com?來了解更多關于優先級的詳細信息。
?
?
?
?
?