網頁展現的更快,官方說法叫做首屏繪制,First Paint 或者簡稱 FP,直白的說法叫做白屏時間,就是從輸入 URL 到真的看到內容(不必可交互,那個叫 TTI, Time to Interactive)之間經歷的時間。當然這個時間越短越好。
但這里要注意,和首屏相關的除了 FP 還有兩個指標,分別稱為 FCP (First Contentful Paint,頁面有效內容的繪制) 和 FMP (First Meaningful Paint,頁面有意義的內容繪制)。雖然這幾個概念可能會讓我們繞暈,但我們只需要了解一點:首屏時間 FP 并不要求內容是真實的,有效的,有意義的,可交互的。換言之,隨便 給用戶看點啥都行。
這就是本文標題的玄機了:“看起來”。是的,只是看起來更快,實際上還是那樣。所以本文并不討論性能優化,討論的是一個投機取巧的小伎倆,但的確能夠實實在在的提升體驗。打個比方,性能優化是修煉內功,提升你本身的各項機能;而本文接下來要討論的是一些招式,能讓你在第一時間就唬住對手。
這所謂的招式就是我接下來要談的內容,學名骨架屏,也叫 Skeleton。你可能沒聽過這個名字,但你不可能沒見過它。
骨架屏長什么樣
這種應該是最常見的形式,使用各種形狀的灰色矩形來模擬圖片和文字。有些 APP 也會使用圓形,但重點都是和實際內容結構近似,不能差距太大。
如果追求效果,還可以在色塊表面添加動畫(如波紋),顯示出一種動態的效果,算是致敬 Loading 了。
在圖片居多的站點,這將會是一個很好的體驗,因為圖片通常加載較慢。如上圖演示中的占位圖片采用了低像素的圖片,即大體配色和變化是和實際內容一致的。
如果無法生成這樣的低像素圖片,稍微降級的方案是通過算法獲取圖片的主體顏色,使用純色塊占位。
再退一級,還可以使用全站相同的站位圖片,或者直接一個統一顏色的色塊。雖說效果肯定不如上面兩種,但也聊勝于無。
骨架屏完全是自定義的,想做成什么樣全憑你的想象。你想做圓形的,三角形的,立體的都可以,但“占位”決定了它的特性:它不能太復雜,必須第一時間,最快展現出來。
骨架屏有哪些優勢
大體來說,骨架屏的優勢在于:
1、在頁面加載初期預先渲染內容,提升感官上的體驗。
2、一般情況骨架屏和實際內容的結構是類似的,因此之后的切換不會過于突兀。這點和傳統的 Loading 動圖不同,可以認為是其升級版。
3、只需要簡單的 CSS 支持 (涉及圖片懶加載可能還需要 JS ),不要求 HTTPS 協議,沒有額外的學習和維護成本。
4、如果頁面采用組件化開發,每個組件可以根據自身狀態定義自身的骨架屏及其切換時機,同時維持了組件之間的獨立性。
骨架屏能用在哪里
現在的 WEB 站點,大致有兩種渲染模式:
前端渲染
由于最近幾年 Angular/React/Vue 的相繼推出和流行,前端渲染開始占據主導。這種模式的應用也叫單頁應用(SPA, Single Page Application)。
前端渲染的模式是服務器(多為靜態服務器)返回一個固定的 HTML。通常這個 HTML 包含一個空的容器節點,沒有其他內容。之后內部包含的 JS 包含路由管理,頁面渲染,頁面切換,綁定事件等等邏輯,所以稱之為前端渲染。
因為前端要管理的事情很多,所以 JS 通常很大很復雜,執行起來也要花較多的時間。在 JS 渲染出實際內容之前,骨架屏就是一個很好的替補隊員。
后端渲染
在這波前端渲染流行之前,早期的傳統網站采用的模式叫做后端渲染,即服務器直接返回網站的 HTML 頁面,已經包含首頁的全部(或絕大部分) DOM 元素。其中包含的 JS 的作用大多是綁定事件,定義用戶交互后的行為等。少量會額外添加/修改一些 DOM,但無礙大局。
此外,前端渲染的模式存在 SEO 不友好的問題,因為它返回的 HTML 是一個空的容器。如果搜索引擎沒有執行 JS 的能力(稱為 Deep Render),那它就不知道你的站點究竟是什么內容,自然也就無法把站點排到搜索結果中去。這對于絕大部分站點來說是不可接受的,于是前端框架又相繼推出了服務端渲染(簡稱 SSR, Server Side Rendering)模式。這個模式和傳統網站很接近,在于返回的 HTML 也是包含所有的 DOM,而非前端渲染。而前端 JS 除了綁定事件之外,還會多做一個事情叫做“激活”(hydration),這里就不再贅述了。
不論是傳統模式還是 SSR,只要是后端渲染,就不需要骨架屏。因為頁面的內容直接存在于 HTML,所以并沒有骨架屏出場的余地。
骨架屏怎么用
討論了一波背景,我們來看如何使用。首先先無視具體的實現細節,先看思路。
實現思路
大體分為幾個步驟:
<html> <head> <style> .skeleton-wrapper { // styles } </style> <!-- 聲明 meta 或者引入其他 CSS --> </head> <body> <div id="app"> <div class="skeleton-wrapper"> <img src="data:image/svg+xml;base64,XXXXXX"> </div> </div> <!-- 引用 JS --> </body> </html>
let app = new Vue({...}) let container = document.querySelector('#app') if (container) { container.innerHTML = '' } app.$mount(container)
僅此兩步,并不牽涉多么復雜的機制和高端的 API,因此非常容易應用,趕快用起來!
示例
我編寫了一個示例,用于快速展現骨架屏的效果,代碼在此。
代碼的三個文件各司其職,配合上面的實現思路,應該還是很好理解的。可以在 這里 查看效果。
因為這個示例的邏輯太過簡單,而實際的前端渲染框架復雜得多,包含的功能也不單純是渲染,還有狀態管理,路由管理,虛擬 DOM 等等,所以文件大小和執行時間都更大更長。我們在查看例子的時候,把網絡調成 "Fast 3G" 或者 "Slow 3G" 能夠稍微真實一些。
但匪夷所思的是,對著這個地址刷新試幾次,我也基本看不到骨架屏(骨架屏的內容是一個居中的藍色方形圖片,外加一條白色橫線反復側滑的高亮動畫)。是我們的實現思路有問題嗎?
瀏覽器的奧秘:減少重排
為了排除肉眼的遺漏和干擾,我們用 Chrome Dev Tools 的 Performance 工具來記錄剛才發生了什么,截圖如下:(截圖時的網絡設置為 "Fast 3G")
我們可以很明顯地看到 3 個時間點:
1、HTML 加載完成了。瀏覽器在解析 HTML 的同時,發現了它需要引用的 2 個外部資源 index.js 和 index.css,于是發送網絡請求去獲取。
2、獲取成功后,執行 JS 并注冊 CSS 的規則。
3、JS 一執行,很自然的渲染出了實際的內容,并應用了樣式規則(隨機顏色的橫條)。
我們的骨架屏呢?按照預想,骨架屏應該出現在 1 和 2 之間,也就是在獲取 JS 和 CSS 的同時,就應該渲染骨架屏了。這也是我們當時把骨架屏的 HTML 注入到 index.html, 還把 CSS 從 index.css 中分離出來的良苦用心,然而瀏覽器并不買賬。
這其實和瀏覽器的渲染順序有關。
相信大家都整理過行李箱。我們在整理行李箱時,會根據每個行李的大小合理安排,大的和小的配合,填滿一層再放上面一層。現在突然有人跑來跟你說,你的電腦不用帶了,你要多帶兩件衣服,你不能帶那么多瓶礦泉水。除了想打他之外,為了重新整理行李箱,必然需要把整理好的行李拿出來再重新放。在瀏覽器中這個過程叫做重排 (reflow),而那個餿主意就是新加載的 CSS。顯而易見,重排的開銷是很大的。
熟能生巧,箱子理多了,就能想出解決辦法。既然每個 CSS 文件加載都可能觸發重繪,那我能不能等所有 CSS 加載完了一起渲染呢?正是基于這一點,瀏覽器會等 HTML 中所有的 CSS 都加載完,注冊完,一起應用樣式,力求一次排列完成工作,不要反復重排。看起來瀏覽器的設計者經常出差,因為這是一個很正確的優化思路,但應用在骨架屏上就出了問題。
我們為了盡早展現骨架屏,把骨架屏的樣式從 index.css 分離出來。但瀏覽器不知道,它以為骨架屏的 HTML 還依賴 index.css,所以必須等它加載完。而它加載完之后,render.js 也差不多加載完開始執行了,于是骨架屏的 HTML 又被替換了,自然就看不到了。而且在等待 JS, CSS 加載的時候依然是個白屏,骨架屏的效果大打折扣。
所以我們要做的是告訴瀏覽器,你放心大膽的先畫骨架屏,它和后面的 index.css 是無關的。那怎么告訴它呢?
告訴瀏覽器先渲染骨架屏
我們在引用 CSS 時,會使用 <link rel="stylesheet" href="xxxx> 這樣的語法。但實際上,瀏覽器還提供了其他一些機制確保(后續)頁面的性能,我們稱之為 preload,中文叫預加載。具體來說,使用 <link rel="preload" href="xxxx">,提前把后續要使用的資源先聲明一下。在瀏覽器空閑的時候會提前加載并放入緩存。之后再使用就可以節省一個網絡請求。
這看似無關的技術,在這里將起到很大的作用,因為 預加載的資源是不會影響當前頁面的。
我們可以通過這種方式,告訴瀏覽器:先不要管 index.css,直接畫骨架屏。之后 index.css加載回來之后,再應用這個樣式。具體來說代碼如下:
<link rel="preload" href="index.css" as="style" onload="this.rel='stylesheet'">
方法的核心是通過改變 rel 可以讓瀏覽器重新界定 <link> 標簽的角色,從預加載變成當頁樣式。(另外也有文章采用修改 media 的方法,但瀏覽器支持度較低,這里不作展開了。我把文章列在最后了)這樣的話,瀏覽器在 CSS 尚未獲取完成時,會先渲染骨架屏(因為此時的 CSS 還是 preload,也就是后續使用的,并不妨礙當前頁面)。而當 CSS 加載完成并修改了自己的 rel之后,瀏覽器重新應用樣式,目的達成。
不得不考慮的注意點
事實上,并不是把 rel="stylesheet" 改成 rel="preload" 就完事兒了。在真正應用到生產環境之前,我們還有很多事情要考慮。
兼容性考慮
首先,在 <link> 內部我們使用了 onload,也就是使用了 JS。為了應對用戶的瀏覽器沒有開啟腳本功能的情況,我們需要添加一個 fallback。(不過這點對于單頁應用來說可能也無所謂,因為如果沒有腳本,那頁面實際內容也渲染不出來的)
<noscript><link rel="stylesheet" href="index.css"></noscript>
其次,rel="preload" 并不是沒有兼容性問題。對于不支持 preload 的瀏覽器,我們可以添加一些 polyfill 代碼(來使所有瀏覽器獲得一致的效果。
<script> /*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */ (function(){ ... }()); </script>
polyfill 的壓縮代碼可以參見 Lavas 的 SPA 模板第 29 行。
加載順序
不同于傳統頁面,我們的實際 DOM 是通過 render.js 生成的。所以如果 JS 先于 CSS 執行,那將會發生跳動。(因為先渲染了實際內容卻沒有樣式,而后樣式加載,頁面出現很明顯的變化)所以這里我們需要嚴格控制 CSS 早于渲染。
<link rel="preload" href="index.css" as="style" onload="this.rel='stylesheet';window.STYLE_READY=true;window.mountApp && window.mountApp()">
JS 對外暴露一個 mountApp 方法用于渲染頁面(其實是模擬 Vue 的 mount)
// render.js function mountApp() { // 方法內部就是把實際內容添加到 <body> 上面 } // 本來直接調用方法完成渲染 // mountApp() // 改成掛到 window 由 CSS 來調用 window.mountApp = mountApp() // 如果 JS 晚于 CSS 加載完成,那直接執行渲染。 if (window.STYLE_READY) { mountApp() }
如果 CSS 更快加載完成,那么通過設置 window.STYLE_READY 允許 JS 加載完成后直接執行;而如果 JS 更快,則先不自己執行,而是把機會留給 CSS 的 onload。
清空 onload
loadCSS 的開發者提出,某些瀏覽器會在 rel 改變時重新出發 onload,導致后面的邏輯走了兩次。為了消除這個影響,我們再在 onload 里面添加一句 this.onload=null。
最終的 CSS 引用方式
<link rel="preload" href="index.css" as="style" onload="this.onload=null;this.rel='stylesheet';window.STYLE_READY=true;window.mountApp && window.mountApp()"> <!-- 為了方便閱讀,折行重復一遍 --> <!-- this.onload=null --> <!-- this.rel='stylesheet' --> <!-- window.STYLE_READY=true --> <!-- window.mountApp && window.mountApp() -->
修改后的效果
修改后的代碼在 這里,訪問地址在 這里。(為了簡便,我省去了處理兼容性的代碼,即 <noscript> 和 preload polyfill)
Performance 截圖如下:(依然采用了 "Fast 3G" 的網絡設置)
這次在 render.js 和 index.css 還在加載的時候頁面已經呈現出骨架屏的內容,實際肉眼也可以觀測到。在截圖的情況下,骨架屏的展現大約持續了 300ms,占據整個網絡請求的大約一半時間。
至于說為什么不是 HTML 加載完成立馬展現骨架屏,而是還要等大約 300ms 才展現,從圖上看是瀏覽器 ParseHTML 所花費的時間,可能在 Dev Tools 打開的情況下計算資源有限,不過可優化空間已經不大。(可能簡化骨架屏的結構能起一些作用吧)
多骨架屏的支持
一般來說一個站點的所有頁面不太可能是同一種展示類型。例如說首頁和內部頁面就展示風格而言會很有區別,另外例如列表頁和搜索頁比較接近(可能都有列表展示),但和詳情頁(可能是商品,服務,個人信息,博客文章等等)就會很不相同。但單頁應用的 index.html 只有一個,所有的變化都源自前端渲染框架在容器節點內部進行改變。所以直接將骨架屏注入到 index.html中會導致所有的頁面都用同一個骨架屏,那就很難達成“和實際內容結構類似”的目標了,骨架屏就退化為 Loading 了。
為了要支持多種骨架屏,我們需要在 index.html 里面進行判斷邏輯(獨立于主體 JS 之外),具體來說:
1、把所有種類的骨架屏的 HTML 和樣式全部寫入 index.html
2、在 index.html 底下新增內聯的腳本 <script>,根據當前路由判斷應該展示哪一個骨架屏
這樣會導致 index.html 體積變大一點,但整體感覺依然是收益大于付出,我認為是值得的。
后記
這個優化點最早由我的前同事 xiaop 同學 在開發 Lavas 的 SPA 模板中發現并完成的,Issue 記錄在此。我在他的基礎上,做了一個分離 Lavas 和 Vue 環境并且更直白的例子,讓截圖也盡可能易于理解,方便閱讀。在此非常感謝他的工作!
另外骨架屏的編寫我全部采用的是純粹的手寫 HTML 和 CSS,不止展現邏輯,包括開發流程也是獨立于單頁應用其他常規頁面的。當然這可能給開發者帶來一點不便,所以這時候需要推出 xiaop 同學的利器:vue-skeleton-webpack-plugin https://github.com/lavas-project/vue-skeleton-webpack-plugin。它的作用是把骨架屏本身也當成一個 Vue 組件,配上單獨的路由規則來統一在 Vue 項目中的開發體驗,最后使用 webpack 在打包構建的時候加以區分并注入,對于使用 Vue + webpack 開發的同學來說可以一試。
參考文章
轉自作者作者:小蘑菇小哥https://zhuanlan.zhihu.com/p/48601348
網頁展現的更快,官方說法叫做首屏繪制,First Paint 或者簡稱 FP,直白的說法叫做白屏時間,就是從輸入 URL 到真的看到內容(不必可交互,那個叫 TTI, Time to Interactive)之間經歷的時間。當然這個時間越短越好。
但這里要注意,和首屏相關的除了 FP 還有兩個指標,分別稱為 FCP (First Contentful Paint,頁面有效內容的繪制) 和 FMP (First Meaningful Paint,頁面有意義的內容繪制)。雖然這幾個概念可能會讓我們繞暈,但我們只需要了解一點:首屏時間 FP 并不要求內容是真實的,有效的,有意義的,可交互的。換言之,隨便 給用戶看點啥都行。
這就是本文標題的玄機了:“看起來”。是的,只是看起來更快,實際上還是那樣。所以本文并不討論性能優化,討論的是一個投機取巧的小伎倆,但的確能夠實實在在的提升體驗。打個比方,性能優化是修煉內功,提升你本身的各項機能;而本文接下來要討論的是一些招式,能讓你在第一時間就唬住對手。
這所謂的招式就是我接下來要談的內容,學名骨架屏,也叫 Skeleton。你可能沒聽過這個名字,但你不可能沒見過它。
骨架屏長什么樣
這種應該是最常見的形式,使用各種形狀的灰色矩形來模擬圖片和文字。有些 APP 也會使用圓形,但重點都是和實際內容結構近似,不能差距太大。
如果追求效果,還可以在色塊表面添加動畫(如波紋),顯示出一種動態的效果,算是致敬 Loading 了。
在圖片居多的站點,這將會是一個很好的體驗,因為圖片通常加載較慢。如上圖演示中的占位圖片采用了低像素的圖片,即大體配色和變化是和實際內容一致的。
如果無法生成這樣的低像素圖片,稍微降級的方案是通過算法獲取圖片的主體顏色,使用純色塊占位。
再退一級,還可以使用全站相同的站位圖片,或者直接一個統一顏色的色塊。雖說效果肯定不如上面兩種,但也聊勝于無。
骨架屏完全是自定義的,想做成什么樣全憑你的想象。你想做圓形的,三角形的,立體的都可以,但“占位”決定了它的特性:它不能太復雜,必須第一時間,最快展現出來。
骨架屏有哪些優勢
大體來說,骨架屏的優勢在于:
1、在頁面加載初期預先渲染內容,提升感官上的體驗。
2、一般情況骨架屏和實際內容的結構是類似的,因此之后的切換不會過于突兀。這點和傳統的 Loading 動圖不同,可以認為是其升級版。
3、只需要簡單的 CSS 支持 (涉及圖片懶加載可能還需要 JS ),不要求 HTTPS 協議,沒有額外的學習和維護成本。
4、如果頁面采用組件化開發,每個組件可以根據自身狀態定義自身的骨架屏及其切換時機,同時維持了組件之間的獨立性。
骨架屏能用在哪里
現在的 WEB 站點,大致有兩種渲染模式:
前端渲染
由于最近幾年 Angular/React/Vue 的相繼推出和流行,前端渲染開始占據主導。這種模式的應用也叫單頁應用(SPA, Single Page Application)。
前端渲染的模式是服務器(多為靜態服務器)返回一個固定的 HTML。通常這個 HTML 包含一個空的容器節點,沒有其他內容。之后內部包含的 JS 包含路由管理,頁面渲染,頁面切換,綁定事件等等邏輯,所以稱之為前端渲染。
因為前端要管理的事情很多,所以 JS 通常很大很復雜,執行起來也要花較多的時間。在 JS 渲染出實際內容之前,骨架屏就是一個很好的替補隊員。
后端渲染
在這波前端渲染流行之前,早期的傳統網站采用的模式叫做后端渲染,即服務器直接返回網站的 HTML 頁面,已經包含首頁的全部(或絕大部分) DOM 元素。其中包含的 JS 的作用大多是綁定事件,定義用戶交互后的行為等。少量會額外添加/修改一些 DOM,但無礙大局。
此外,前端渲染的模式存在 SEO 不友好的問題,因為它返回的 HTML 是一個空的容器。如果搜索引擎沒有執行 JS 的能力(稱為 Deep Render),那它就不知道你的站點究竟是什么內容,自然也就無法把站點排到搜索結果中去。這對于絕大部分站點來說是不可接受的,于是前端框架又相繼推出了服務端渲染(簡稱 SSR, Server Side Rendering)模式。這個模式和傳統網站很接近,在于返回的 HTML 也是包含所有的 DOM,而非前端渲染。而前端 JS 除了綁定事件之外,還會多做一個事情叫做“激活”(hydration),這里就不再贅述了。
不論是傳統模式還是 SSR,只要是后端渲染,就不需要骨架屏。因為頁面的內容直接存在于 HTML,所以并沒有骨架屏出場的余地。
骨架屏怎么用
討論了一波背景,我們來看如何使用。首先先無視具體的實現細節,先看思路。
實現思路
大體分為幾個步驟:
<html> <head> <style> .skeleton-wrapper { // styles } </style> <!-- 聲明 meta 或者引入其他 CSS --> </head> <body> <div id="app"> <div class="skeleton-wrapper"> <img src="data:image/svg+xml;base64,XXXXXX"> </div> </div> <!-- 引用 JS --> </body> </html>
let app = new Vue({...}) let container = document.querySelector('#app') if (container) { container.innerHTML = '' } app.$mount(container)
僅此兩步,并不牽涉多么復雜的機制和高端的 API,因此非常容易應用,趕快用起來!
示例
我編寫了一個示例,用于快速展現骨架屏的效果,代碼在此。
代碼的三個文件各司其職,配合上面的實現思路,應該還是很好理解的。可以在 這里 查看效果。
因為這個示例的邏輯太過簡單,而實際的前端渲染框架復雜得多,包含的功能也不單純是渲染,還有狀態管理,路由管理,虛擬 DOM 等等,所以文件大小和執行時間都更大更長。我們在查看例子的時候,把網絡調成 "Fast 3G" 或者 "Slow 3G" 能夠稍微真實一些。
但匪夷所思的是,對著這個地址刷新試幾次,我也基本看不到骨架屏(骨架屏的內容是一個居中的藍色方形圖片,外加一條白色橫線反復側滑的高亮動畫)。是我們的實現思路有問題嗎?
瀏覽器的奧秘:減少重排
為了排除肉眼的遺漏和干擾,我們用 Chrome Dev Tools 的 Performance 工具來記錄剛才發生了什么,截圖如下:(截圖時的網絡設置為 "Fast 3G")
我們可以很明顯地看到 3 個時間點:
1、HTML 加載完成了。瀏覽器在解析 HTML 的同時,發現了它需要引用的 2 個外部資源 index.js 和 index.css,于是發送網絡請求去獲取。
2、獲取成功后,執行 JS 并注冊 CSS 的規則。
3、JS 一執行,很自然的渲染出了實際的內容,并應用了樣式規則(隨機顏色的橫條)。
我們的骨架屏呢?按照預想,骨架屏應該出現在 1 和 2 之間,也就是在獲取 JS 和 CSS 的同時,就應該渲染骨架屏了。這也是我們當時把骨架屏的 HTML 注入到 index.html, 還把 CSS 從 index.css 中分離出來的良苦用心,然而瀏覽器并不買賬。
這其實和瀏覽器的渲染順序有關。
相信大家都整理過行李箱。我們在整理行李箱時,會根據每個行李的大小合理安排,大的和小的配合,填滿一層再放上面一層。現在突然有人跑來跟你說,你的電腦不用帶了,你要多帶兩件衣服,你不能帶那么多瓶礦泉水。除了想打他之外,為了重新整理行李箱,必然需要把整理好的行李拿出來再重新放。在瀏覽器中這個過程叫做重排 (reflow),而那個餿主意就是新加載的 CSS。顯而易見,重排的開銷是很大的。
熟能生巧,箱子理多了,就能想出解決辦法。既然每個 CSS 文件加載都可能觸發重繪,那我能不能等所有 CSS 加載完了一起渲染呢?正是基于這一點,瀏覽器會等 HTML 中所有的 CSS 都加載完,注冊完,一起應用樣式,力求一次排列完成工作,不要反復重排。看起來瀏覽器的設計者經常出差,因為這是一個很正確的優化思路,但應用在骨架屏上就出了問題。
我們為了盡早展現骨架屏,把骨架屏的樣式從 index.css 分離出來。但瀏覽器不知道,它以為骨架屏的 HTML 還依賴 index.css,所以必須等它加載完。而它加載完之后,render.js 也差不多加載完開始執行了,于是骨架屏的 HTML 又被替換了,自然就看不到了。而且在等待 JS, CSS 加載的時候依然是個白屏,骨架屏的效果大打折扣。
所以我們要做的是告訴瀏覽器,你放心大膽的先畫骨架屏,它和后面的 index.css 是無關的。那怎么告訴它呢?
告訴瀏覽器先渲染骨架屏
我們在引用 CSS 時,會使用 <link rel="stylesheet" href="xxxx> 這樣的語法。但實際上,瀏覽器還提供了其他一些機制確保(后續)頁面的性能,我們稱之為 preload,中文叫預加載。具體來說,使用 <link rel="preload" href="xxxx">,提前把后續要使用的資源先聲明一下。在瀏覽器空閑的時候會提前加載并放入緩存。之后再使用就可以節省一個網絡請求。
這看似無關的技術,在這里將起到很大的作用,因為 預加載的資源是不會影響當前頁面的。
我們可以通過這種方式,告訴瀏覽器:先不要管 index.css,直接畫骨架屏。之后 index.css加載回來之后,再應用這個樣式。具體來說代碼如下:
<link rel="preload" href="index.css" as="style" onload="this.rel='stylesheet'">
方法的核心是通過改變 rel 可以讓瀏覽器重新界定 <link> 標簽的角色,從預加載變成當頁樣式。(另外也有文章采用修改 media 的方法,但瀏覽器支持度較低,這里不作展開了。我把文章列在最后了)這樣的話,瀏覽器在 CSS 尚未獲取完成時,會先渲染骨架屏(因為此時的 CSS 還是 preload,也就是后續使用的,并不妨礙當前頁面)。而當 CSS 加載完成并修改了自己的 rel之后,瀏覽器重新應用樣式,目的達成。
不得不考慮的注意點
事實上,并不是把 rel="stylesheet" 改成 rel="preload" 就完事兒了。在真正應用到生產環境之前,我們還有很多事情要考慮。
兼容性考慮
首先,在 <link> 內部我們使用了 onload,也就是使用了 JS。為了應對用戶的瀏覽器沒有開啟腳本功能的情況,我們需要添加一個 fallback。(不過這點對于單頁應用來說可能也無所謂,因為如果沒有腳本,那頁面實際內容也渲染不出來的)
<noscript><link rel="stylesheet" href="index.css"></noscript>
其次,rel="preload" 并不是沒有兼容性問題。對于不支持 preload 的瀏覽器,我們可以添加一些 polyfill 代碼(來使所有瀏覽器獲得一致的效果。
<script> /*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */ (function(){ ... }()); </script>
polyfill 的壓縮代碼可以參見 Lavas 的 SPA 模板第 29 行。
加載順序
不同于傳統頁面,我們的實際 DOM 是通過 render.js 生成的。所以如果 JS 先于 CSS 執行,那將會發生跳動。(因為先渲染了實際內容卻沒有樣式,而后樣式加載,頁面出現很明顯的變化)所以這里我們需要嚴格控制 CSS 早于渲染。
<link rel="preload" href="index.css" as="style" onload="this.rel='stylesheet';window.STYLE_READY=true;window.mountApp && window.mountApp()">
JS 對外暴露一個 mountApp 方法用于渲染頁面(其實是模擬 Vue 的 mount)
// render.js function mountApp() { // 方法內部就是把實際內容添加到 <body> 上面 } // 本來直接調用方法完成渲染 // mountApp() // 改成掛到 window 由 CSS 來調用 window.mountApp = mountApp() // 如果 JS 晚于 CSS 加載完成,那直接執行渲染。 if (window.STYLE_READY) { mountApp() }
如果 CSS 更快加載完成,那么通過設置 window.STYLE_READY 允許 JS 加載完成后直接執行;而如果 JS 更快,則先不自己執行,而是把機會留給 CSS 的 onload。
清空 onload
loadCSS 的開發者提出,某些瀏覽器會在 rel 改變時重新出發 onload,導致后面的邏輯走了兩次。為了消除這個影響,我們再在 onload 里面添加一句 this.onload=null。
最終的 CSS 引用方式
<link rel="preload" href="index.css" as="style" onload="this.onload=null;this.rel='stylesheet';window.STYLE_READY=true;window.mountApp && window.mountApp()"> <!-- 為了方便閱讀,折行重復一遍 --> <!-- this.onload=null --> <!-- this.rel='stylesheet' --> <!-- window.STYLE_READY=true --> <!-- window.mountApp && window.mountApp() -->
修改后的效果
修改后的代碼在 這里,訪問地址在 這里。(為了簡便,我省去了處理兼容性的代碼,即 <noscript> 和 preload polyfill)
Performance 截圖如下:(依然采用了 "Fast 3G" 的網絡設置)
這次在 render.js 和 index.css 還在加載的時候頁面已經呈現出骨架屏的內容,實際肉眼也可以觀測到。在截圖的情況下,骨架屏的展現大約持續了 300ms,占據整個網絡請求的大約一半時間。
至于說為什么不是 HTML 加載完成立馬展現骨架屏,而是還要等大約 300ms 才展現,從圖上看是瀏覽器 ParseHTML 所花費的時間,可能在 Dev Tools 打開的情況下計算資源有限,不過可優化空間已經不大。(可能簡化骨架屏的結構能起一些作用吧)
多骨架屏的支持
一般來說一個站點的所有頁面不太可能是同一種展示類型。例如說首頁和內部頁面就展示風格而言會很有區別,另外例如列表頁和搜索頁比較接近(可能都有列表展示),但和詳情頁(可能是商品,服務,個人信息,博客文章等等)就會很不相同。但單頁應用的 index.html 只有一個,所有的變化都源自前端渲染框架在容器節點內部進行改變。所以直接將骨架屏注入到 index.html中會導致所有的頁面都用同一個骨架屏,那就很難達成“和實際內容結構類似”的目標了,骨架屏就退化為 Loading 了。
為了要支持多種骨架屏,我們需要在 index.html 里面進行判斷邏輯(獨立于主體 JS 之外),具體來說:
1、把所有種類的骨架屏的 HTML 和樣式全部寫入 index.html
2、在 index.html 底下新增內聯的腳本 <script>,根據當前路由判斷應該展示哪一個骨架屏
這樣會導致 index.html 體積變大一點,但整體感覺依然是收益大于付出,我認為是值得的。
后記
這個優化點最早由我的前同事 xiaop 同學 在開發 Lavas 的 SPA 模板中發現并完成的,Issue 記錄在此。我在他的基礎上,做了一個分離 Lavas 和 Vue 環境并且更直白的例子,讓截圖也盡可能易于理解,方便閱讀。在此非常感謝他的工作!
另外骨架屏的編寫我全部采用的是純粹的手寫 HTML 和 CSS,不止展現邏輯,包括開發流程也是獨立于單頁應用其他常規頁面的。當然這可能給開發者帶來一點不便,所以這時候需要推出 xiaop 同學的利器:vue-skeleton-webpack-plugin https://github.com/lavas-project/vue-skeleton-webpack-plugin。它的作用是把骨架屏本身也當成一個 Vue 組件,配上單獨的路由規則來統一在 Vue 項目中的開發體驗,最后使用 webpack 在打包構建的時候加以區分并注入,對于使用 Vue + webpack 開發的同學來說可以一試。
參考文章
言
金九銀十跳槽季,offer快到碗里來,前端面試考點眾多,而網上各個知識點的博客文章又太多,看的眼花繚亂...所以我整理了一下常見知識點的精華文章,每個知識點的文章控制在3篇以內,盡量覆蓋該知識點的下容易被面試到的所有內容,文章都是之前自己讀過的,確定是精華干貨。文章會一直更新,也歡迎大家推薦精華文章,大家共同學習進步呀!
display:flex; 在父元素設置,子元素受彈性盒影響,默認排成一行,如果超出一行,按比例壓縮 flex:1; 子元素設置,設置子元素如何分配父元素的空間,flex:1,子元素寬度占滿整個父元素align-items:center 定義子元素在父容器中的對齊方式,center 垂直居中justify-content:center 設置子元素在父元素中居中,前提是子元素沒有把父元素占滿,讓子元素水平居中。
transtion transition-property 規定設置過渡效果的 CSS 屬性的名稱。
transition-duration 規定完成過渡效果需要多少秒或毫秒。
transition-timing-function 規定速度效果的速度曲線。
transition-delay 定義過渡效果何時開始。
animation屬性可以像Flash制作動畫一樣,通過控制關鍵幀來控制動畫的每一步,實現更為復雜的動畫效果。
ainimation實現動畫效果主要由兩部分組成:
通過類似Flash動畫中的幀來聲明一個動畫;
在animation屬性中調用關鍵幀聲明的動畫。
translate 3D建模效果
圖片中的 alt屬性是在圖片不能正常顯示時出現的文本提示。alt有利于SEO優化
圖片中的 title屬性是在鼠標在移動到元素上的文本提示。
復制代碼 <style>
div {
width: 0;
height: 0;
border-top: 40px solid transparent;
border-left: 40px solid transparent;
border-right: 40px solid transparent;
border-bottom: 40px solid #ff0000;
}
</style>
</head>
<body>
<div></div>
</body>
標準盒子模型:寬度=內容的寬度(content)+ border + padding
低版本IE盒子模型:寬度=內容寬度(content+border+padding)
已知寬度,block元素 ,添加添加margin:0 auto屬性。
已知寬度,絕對定位的居中 ,上下左右都為0,margin:auto
復制代碼div {
position: relative / fixed; /* 相對定位或絕對定位均可 */
width:500px;
height:300px;
top: 50%;
left: 50%;
margin-top:-150px;
margin-left:-250px;
外邊距為自身寬高的一半 */
background-color: pink; /* 方便看效果 */
}
.container {
display: flex;
align-items: center; /* 垂直居中 */
justify-content: center; /* 水平居中 */
}
.container div {
width: 100px; /* 可省 */
height: 100px; /* 可省 */
background-color: pink; /* 方便看效果 */
}
clear清除浮動(添加空div法)在浮動元素下方添加空div,并給該元素寫css樣式 {clear:both;height:0;overflow:hidden;}
給浮動元素父級設置高度
父級同時浮動(需要給父級同級元素添加浮動)
父級設置成inline-block,其margin: 0 auto居中方式失效
給父級添加overflow:hidden 清除浮動方法
萬能清除法 after偽類 清浮動(現在主流方法,推薦使用)
復制代碼float_div:after{
content:".";
clear:both;
display:block;
height:0;
overflow:hidden;
visibility:hidden;
}
.float_div{
zoom:1
}
圣杯布局/雙飛翼布局
復制代碼 <style>
* {
margin: 0;
padding: 0;
}
.middle,
.left,
.right {
position: relative;
float: left;
min-height: 130px;
}
.container {
padding: 0 220px 0 200px;
overflow: hidden;
}
.left {
margin-left: -100%;
left: -200px;
width: 200px;
background: red;
}
.right {
margin-left: -220px;
right: -220px;
width: 220px;
background: green;
}
.middle {
width: 100%;
background: blue;
word-break: break-all;
}
</style>
</head>
<body>
<div class='container'>
<div class='middle'></div>
<div class='left'></div>
<div class='right'></div>
</div>
</body>
display:none 隱藏對應的元素,在文檔布局中不再給它分配空間,它各邊的元素會合攏,就當他從來不存在。
visibility:hidden 隱藏對應的元素,但是在文檔布局中仍保留原來的空間。
link屬于HTML標簽,而@import是CSS提供的頁面被加載的時,link會同時被加載,而@import引用的CSS會等到頁面被加載完再加載
import只在IE5以上才能識別,而link是HTML標簽,無兼容問題
link方式的樣式的權重 高于@import的權重.
共同點: 改變行內元素的呈現方式,display被置為block 讓元素脫離普通流,不占據空間 默認會覆蓋到非定位元素上
不同點: absolute的”根元素“是可以設置的 fixed的”根元素“固定為瀏覽器窗口。當你滾動網頁,fixed元素與瀏覽器窗口之間的距離是不變的。
Animation和transition大部分屬性是相同的,他們都是隨時間改變元素的屬性值,他們的主要區別是transition需要觸發一個事件才能改變屬性, 而animation不需要觸發任何事件的情況下才會隨時間改變屬性值,并且transition為2幀,從from .... to,而animation可以一幀一幀的。
transition 規定動畫的名字 規定完成過渡效果需要多少秒或毫秒 規定速度效果 定義過渡效果何時開始 animation 指定要綁定到選擇器的關鍵幀的名稱
復制代碼不同級別:總結排序:!important > 行內樣式>ID選擇器 > 類選擇器 > 標簽 > 通配符 > 繼承 > 瀏覽器默認屬性
1.屬性后面加!import 會覆蓋頁面內任何位置定義的元素樣式
2.作為style屬性寫在元素內的樣式
3.id選擇器
4.類選擇器
5.標簽選擇器
6.通配符選擇器(*)
7.瀏覽器自定義或繼承
**同一級別:后寫的會覆蓋先寫的**
css選擇器的解析原則:選擇器定位DOM元素是從右往左的方向,這樣可以盡早的過濾掉一些不必要的樣式規則和元素
復制代碼 多個圖片集成在一個圖片中的圖
使用雪碧圖可以減少網絡請求的次數,加快允許的速度
通過background-position,去定位圖片在屏幕的哪個位置
相同點: 都常用來判斷一個變量是否為空,或者是什么類型的。
不同點: typeof 返回值是一個字符串,用來說明變量的數據類型 instanceof 用于判斷一個變量是否屬于某個對象的實例.
復制代碼visibility:hidden、display:none、z-index=-1、opacity:0
1.opacity:0,該元素隱藏起來了,但不會改變頁面布局,并且,如果該元素已經綁定了一些事件,如click事件也能觸發
2.visibility:hidden,該元素隱藏起來了,但不會改變頁面布局,但是不會觸發該元素已經綁定的事件
3.display:node, 把元素隱藏起來,并且會改變頁面布局,可以理解成在頁面中把該元素刪掉
淺克隆: 只是拷貝了基本類型的數據,而引用類型數據,復制后也是會發生引用,我們把這種拷貝叫做“(淺復制)淺拷貝”,換句話說,淺復制僅僅是指向被復制的內存地址,如果原地址中對象被改變了,那么淺復制出來的對象也會相應改變。
深克隆: 創建一個新對象,屬性中引用的其他對象也會被克隆,不再指向原有對象地址。 JSON.parse、JSON.stringify()
let定義塊級作用域變量 沒有變量的提升,必須先聲明后使用 let聲明的變量,不能與前面的let,var,conset聲明的變量重名
const 定義只讀變量 const聲明變量的同時必須賦值,const聲明的變量必須初始化,一旦初始化完畢就不允許修改 const聲明變量也是一個塊級作用域變量 const聲明的變量沒有“變量的提升”,必須先聲明后使用 const聲明的變量不能與前面的let, var , const聲明的變量重 const定義的對象\數組中的屬性值可以修改,基礎數據類型不可以
ES6可以給形參函數設置默認值
在數組之前加上三個點(...)展開運算符
數組的解構賦值、對象的解構賦值
箭頭函數的特點 箭頭函數相當于匿名函數,是不能作為構造函數的,不能被new 箭頭函數沒有arguments實參集合,取而代之用...剩余運算符解決 箭頭函數沒有自己的this。他的this是繼承當前上下文中的this 箭頭函數沒有函數原型 箭頭函數不能當做Generator函數,不能使用yield關鍵字 不能使用call、apply、bind改變箭頭函數中this指向 Set數據結構,數組去重
=賦值
==返回一個布爾值;相等返回true,不相等返回false; 允許不同數據類型之間的比較; 如果是不同類型的數據進行,會默認進行數據類型之間的轉換; 如果是對象數據類型的比較,比較的是空間地址
=== 只要數據類型不一樣,就返回false;
復制代碼1、js工廠模式
2、js構造函數模式
3、js原型模式
4、構造函數+原型的js混合模式
5、構造函數+原型的動態原型模式
6、觀察者模式
7、發布訂閱模式
call() 和apply()的第一個參數相同,就是指定的對象。這個對象就是該函數的執行上下文。
call()和apply()的區別就在于,兩者之間的參數。
call()在第一個參數之后的 后續所有參數就是傳入該函數的值。
apply() 只有兩個參數,第一個是對象,第二個是數組,這個數組就是該函數的參數。 bind() 方法和前兩者不同在于: bind() 方法會返回執行上下文被改變的函數而不會立即執行,而前兩者是 直接執行該函數。他的參數和call()相同。
原型鏈繼承 核心: 將父類的實例作為子類的原型
構造繼承 核心:使用父類的構造函數來增強子類實例,等于是復制父類的實例屬性給子類
實例繼承 核心:為父類實例添加新特性,作為子類實例返回
拷貝繼承
組合繼承 核心:通過調用父類構造,繼承父類的屬性并保留傳參的優點,然后通過將父類實例作為子類原型,實現 函數復用
寄生組合繼承 核心:通過寄生方式,砍掉父類的實例屬性,這樣,在調用兩次父類的構造的時候,就不會初始化兩次實 例方法/屬性,避免的組合繼承的缺點
個人感覺,簡單來說閉包就是在函數里面聲明函數,本質上說就是在函數內部和函數外部搭建起一座橋梁,使得子函數可以訪問父函數中所有的局部變量,但是反之不可以,這只是閉包的作用之一,另一個作用,則是保護變量不受外界污染,使其一直存在內存中,在工作中我們還是少使用閉包的好,因為閉包太消耗內存,不到萬不得已的時候盡量不使用。
把所有的對象共用的屬性全部放在堆內存的一個對象(共用屬性組成的對象),然后讓每一個對象的 __proto__存儲這個「共用屬性組成的對象」的地址。而這個共用屬性就是原型,原型出現的目的就是為了減少不必要的內存消耗。而原型鏈就是對象通過__proto__向當前實例所屬類的原型上查找屬性或方法的機制,如果找到Object的原型上還是沒有找到想要的屬性或者是方法則查找結束,最終會返回undefined
將html代碼按照深度優先遍歷來生成DOM樹。 css文件下載完后也會進行渲染,生成相應的CSSOM。 當所有的css文件下載完且所有的CSSOM構建結束后,就會和DOM一起生成Render Tree。 接下來,瀏覽器就會進入Layout環節,將所有的節點位置計算出來。 最后,通過Painting環節將所有的節點內容呈現到屏幕上。
復制代碼1、瀏覽器的地址欄輸入URL并按下回車。
2、瀏覽器查找當前URL是否存在緩存,并比較緩存是否過期。3、DNS解析URL對應的IP。
4、根據IP建立TCP連接(三次握手)。
5、HTTP發起請求。
6、服務器處理請求,瀏覽器接收HTTP響應。
7、渲染頁面,構建DOM樹。
8、關閉TCP連接(四次揮手)
相同點 都是保存在瀏覽器端,且同源的。
不同點
同源策略(協議+端口號+域名要相同)
1、jsonp跨域(只能解決get) 原理:動態創建一個script標簽。利用script標簽的src屬性不受同源策略限制,因為所有的src屬性和href屬性都不受同源策略的限制,可以請求第三方服務器資源內容
步驟: 1).去創建一個script標簽 2).script的src屬性設置接口地址 3).接口參數,必須要帶一個自定義函數名,要不然后臺無法返回數據 4).通過定義函數名去接受返回的數據
2、document.domain 基礎域名相同 子域名不同
3、window.name 利用在一個瀏覽器窗口內,載入所有的域名都是共享一個window.name
4、服務器設置對CORS的支持 原理:服務器設置Access-Control-Allow-Origin HTTP響應頭之后,瀏覽器將會允許跨域請求
5、利用h5新特性window.postMessage()
1.創建ajax實例
2.執行open 確定要訪問的鏈接 以及同步異步
3.監聽請求狀態
4.發送請求
ES6的set對象 先將原數組排序,在與相鄰的進行比較,如果不同則存入新數組
復制代碼function unique(arr){
var arr2 = arr.sort();
var res = [arr2[0]];
for(var i=1;i<arr2.length;i++){
if(arr2[i] !== res[res.length-1]){
res.push(arr2[i]);
}
}
return res;
}
利用下標查詢
function unique(arr){
var newArr = [arr[0]];
for(var i=1;i<arr.length;i++){
if(newArr.indexOf(arr[i]) == -1){
newArr.push(arr[i]);
}
}
return newArr;
}
2開頭
3開頭
以4開頭的都是客戶端的問題;
以5開頭都是服務端的問題
同步:在同一時間內做一件事情
異步:在同一時間內做多個事情 JS是單線程的,每次只能做一件事情,JS運行在瀏覽器中,瀏覽器是多線程的,可以在同一時間執行多個任務。
定時器、ajax、事件綁定、回調函數、async await、promise
三次握手
四次揮手
建立連接的時候, 服務器在LISTEN狀態下,收到建立連接請求的SYN報文后,把ACK和SYN放在一個報文里發送給客戶端。 而關閉連接時,服務器收到對方的FIN報文時,僅僅表示對方不再發送數據了但是還能接收數據,而自己也未必全部數據都發送給對方了,所以己方可以立即關閉,也可以發送一些數據給對方后,再發送FIN報文給對方來表示同意現在關閉連接,因此,己方ACK和FIN一般都會分開發送,從而導致多了一次。
如果元素類型發生變化,直接替換 如果是文本,則比較文本里面的內容,是否有差異,如果是元素就需要比較當前元素的屬性是否相等,會先比較key, 在比較類型 為什么 react中循環 建議不要使用索引 ,如果純為了展示 那可以使用索引
全局作用域
私有作用域
塊級作用域
上級作用域
他是ES6中新增加的一個類(new Promise),目的是為了管理JS中的異步編程的,所以把他稱為“Promise設計模式” new Promise 經歷三個狀態:padding(準備狀態:初始化成功、開始執行異步的任務)、fullfilled(成功狀態)、rejected(失敗狀態)== Promise本身是同步編程的,他可以管理異步操作的(重點),new Promise的時候,會把傳遞的函數立即執行 Promise函數天生有兩個參數,resolve(當異步操作執行成功,執行resolve方法),rejected(當異步操作失敗,執行reject方法) then()方法中有兩個函數,第一個傳遞的函數是resolve,第二個傳遞的函數是reject ajax中false代表同步,true代表異步,如果使用異步,不等ajax徹底完成
相同點
不同點
注意:forEach對于空數組是不會調用回調函數的。
async/await函數是異步代碼的新方式
async/await是基于promise實現的
async/await使異步代碼更像同步代碼
await 只能在async函數中使用,不能再普通函數中使用,要成對出現
默認返回一個promise實例,不能被改變
await下面的代碼是異步,后面的代碼是同步的
全局作用域下的this指向window 如果給元素的事件行為綁定函數,那么函數中的this指向當前被綁定的那個元素 函數中的this,要看函數執行前有沒有 . , 有 . 的話,點前面是誰,this就指向誰,如果沒有點,指向window 自執行函數中的this永遠指向window 定時器中函數的this指向window 構造函數中的this指向當前的實例 call、apply、bind可以改變函數的this指向 箭頭函數中沒有this,如果輸出this,就會輸出箭頭函數定義時所在的作用域中的this
所有的函數數據類型都天生自帶一個prototype屬性,該屬性的屬性值是一個對象 prototype的屬性值中天生自帶一個constructor屬性,其constructor屬性值指向當前原型所屬的類 所有的對象數據類型,都天生自帶一個_proto_屬性,該屬性的屬性值指向當前實例所屬類的原型
復制代碼promise、generator、async/await
promise: 1.是一個對象,用來傳遞異步操作的信息。代表著某個未來才會知道結果的時間,并未這個事件提供統一的api,供進異步處理
2.有了這個對象,就可以讓異步操作以同步的操作的流程來表達出來,避免層層嵌套的回調地獄
3.promise代表一個異步狀態,有三個狀態pending(進行中),Resolve(以完成),Reject(失敗)
4.一旦狀態改變,就不會在變。任何時候都可以得到結果。從進行中變為以完成或者失敗
promise.all() 里面狀態都改變,那就會輸出,得到一個數組
promise.race() 里面只有一個狀態變為rejected或者fulfilled即輸出
promis.finally()不管指定不管Promise對象最后狀態如何,都會執行的操作(本質上還是then方法的特例)
復制代碼事件流描述的是從頁面中接受事件的順序,事件 捕獲階段 處于目標階段 事件冒泡階段 addeventListener 最后這個布爾值參數如果是true,表示在捕獲階段調用事件處理程序;如果是false,表示在冒泡階段調用事件處理程序。
1、事件捕獲階段:實際目標div在捕獲階段不會接受事件,也就是在捕獲階段,事件從document到<html>再到<body>就停止了。
2、處于目標階段:事件在div發生并處理,但是事件處理會被看成是冒泡階段的一部分。
3、冒泡階段:事件又傳播回文檔
阻止冒泡事件event.stopPropagation()
function stopBubble(e) {
if (e && e.stopPropagation) { // 如果提供了事件對象event 這說明不是IE瀏覽器
e.stopPropagation()
} else {
window.event.cancelBubble = true //IE方式阻止冒泡
}
}
阻止默認行為event.preventDefault()
function stopDefault(e) {
if (e && e.preventDefault) {
e.preventDefault()
} else {
// IE瀏覽器阻止函數器默認動作的行為
window.event.returnValue = false
}
}
在DOM標準事件模型中,是先捕獲后冒泡。但是如果要實現先冒泡后捕獲的效果, 對于同一個事件,監聽捕獲和冒泡,分別對應相應的處理函數,監聽到捕獲事件,先暫緩執行,直到冒泡事件被捕獲后再執行捕獲事件。
復制代碼千萬不要使用typeof來判斷對象和數組,因為這種類型都會返回object。
typeOf()是判斷基本類型的Boolean,Number,symbol, undefined, String。 對于引用類型:除function,都返回object null返回object。
installOf() 用來判斷A是否是B的實例,installof檢查的是原型。
toString() 是Object的原型方法,對于 Object 對象,直接調用 toString() 就能返回 [Object Object] 。而對于其他對象,則需要通過 call / apply 來調用才能返回正確的類型信息。
hasOwnProperty()方法返回一個布爾值,指示對象自身屬性中是否具有指定的屬性,該方法會忽略掉那些從原型鏈上繼承到的屬性。
isProperty()方法測試一個對象是否存在另一個對象的原型鏈上。
復制代碼因為js是單線程的。瀏覽器遇到etTimeout 和 setInterval會先執行完當前的代碼塊,在此之前會把定時器推入瀏覽器的
待執行時間隊列里面,等到瀏覽器執行完當前代碼之后會看下事件隊列里有沒有任務,有的話才執行定時器里的代碼
復制代碼 1.slice(start,end):方法可以從已有數組中返回選定的元素,返回一個新數組,
包含從start到end(不包含該元素)的數組方法
注意:該方法不會更新原數組,而是返回一個子數組
2.splice():該方法想或者從數組中添加或刪除項目,返回被刪除的項目。(該方法會改變原數組)
splice(index, howmany,item1,...itemx)
·index參數:必須,整數規定添加或刪除的位置,使用負數,從數組尾部規定位置
·howmany參數:必須,要刪除的數量,
·item1..itemx:可選,向數組添加新項目
3.map():會返回一個全新的數組。使用于改變數據值的時候。會分配內存存儲空間數組并返回,forEach()不會返回數據
4.forEach(): 不會返回任何有價值的東西,并且不打算改變數據,單純的只是想用數據做一些事情,他允許callback更改原始數組的元素
5.reduce(): 方法接收一個函數作為累加器,數組中的每一個值(從左到右)開始縮減,最終計算一個值,不會改變原數組的值
6.filter(): 方法創建一個新數組,新數組中的元素是通過檢查指定數組中符合條件的所有元素。它里面通過function去做處理
vue是一個漸進式的JS框架。他易用,靈活,高效; 可以把一個頁面分隔成多個組件;當其他頁面有類似功能時,直接讓封裝的組件進行復用; 他是構建用戶界面的聲明式框架,只關心圖層;不關心具體是如何實現的
Vue的雙向數據綁定是由數據劫持結合發布者訂閱者實現的。 數據劫持是通過Object.defineProperty()來劫持對象數據的setter和getter操作。 在數據變動時作你想做的事
原理 通過Observer來監聽自己的model數據變化,通過Compile來解析編譯模板指令,最終利用Watcher搭起Observer和Compile之間的通信橋梁,達到數據變化->視圖更新 在初始化vue實例時,遍歷data這個對象,給每一個鍵值對利用Object.definedProperty對data的鍵值對新增get和set方法,利用了事件監聽DOM的機制,讓視圖去改變數據
react整體是函數式的思想,把組件設計成純組件,狀態和邏輯通過參數傳入,所以在react中,是單向數據流;
vue的思想是響應式的,也就是基于是數據可變的,通過對每一個屬性建立Watcher來監聽,當屬性變化的時候,響應式的更新對應的虛擬dom。
復制代碼頁面通過mapAction異步提交事件到action。action通過commit把對應參數同步提交到mutation。
mutation會修改state中對于的值。 最后通過getter把對應值跑出去,在頁面的計算屬性中
通過mapGetter來動態獲取state中的值
state中保存著共有數據,數據是響應式的 getter可以對state進行計算操作,主要用來過濾一些數據,可以在多組件之間復用 mutations定義的方法動態修改state中的數據,通過commit提交方法,方法必須是同步的 actions將mutations里面處理數據的方法變成異步的,就是異步操作數據,通store.dispatch來分發actions,把異步的方法寫在actions中,通過commit提交mutations,進行修改數據。 modules:模塊化vuex
hash ——即地址欄URL中的#符號(此hsah 不是密碼學里的散列運算) hash 雖然出現URL中,但不會被包含在HTTP請求中,對后端完全沒有影響,因此改變hash不會重新加載頁面。 history ——利用了HTML5 History Interface 中新增的pushState() 和replaceState() 方法
這兩個方法應用于瀏覽器的歷史記錄站,在當前已有的back、forward、go 的基礎之上,它們提供了對歷史記錄進行修改的功能。只是當它們執行修改是,雖然改變了當前的URL,但你瀏覽器不會立即向后端發送請求。
當 Vue.js 用v-for正在更新已渲染過的元素列表時,它默認用“就地復用”策略。 如果數據項的順序被改變,Vue 將不會移動 DOM 元素來匹配數據項的順序,而是簡單復用此處每個元素,并且確保它在特定索引下顯示已被渲染過的每個元素。
key的作用主要是為了高效的更新虛擬DOM。
$route是“路由信息對象”,包括path,params,hash,query,fullPath,matched,name等路由信息參數。 $router是“路由實例”對象包括了路由的跳轉方法,鉤子函數等。
導航守衛 router.beforeEach 全局前置守衛
復制代碼// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next) => {
next();
});
router.beforeResolve((to, from, next) => {
next();
});
router.afterEach((to, from) => {
console.log('afterEach 全局后置鉤子');
});
路由獨享的守衛 你可以在路由配置上直接定義 beforeEnter 守衛
復制代碼const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
組件內的守衛 你可以在路由組件內直接定義以下路由導航守衛
復制代碼const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染該組件的對應路由被 confirm 前調用
// 不!能!獲取組件實例 `this`
// 因為當守衛執行前,組件實例還沒被創建
},
beforeRouteUpdate (to, from, next) {
// 在當前路由改變,但是該組件被復用時調用
// 舉例來說,對于一個帶有動態參數的路徑 /foo/:id,在 /foo/1 和 /foo/2 之間跳轉的時候,
// 由于會渲染同樣的 Foo 組件,因此組件實例會被復用。而這個鉤子就會在這個情況下被調用。
// 可以訪問組件實例 `this`
},
beforeRouteLeave (to, from, next) {
// 導航離開該組件的對應路由時調用,我們用它來禁止用戶離開
// 可以訪問組件實例 `this`
// 比如還未保存草稿,或者在用戶離開前,
將setInterval銷毀,防止離開之后,定時器還在調用。
}
}
請求后臺資源的模塊。
復制代碼$ npm install axios -S裝好
然后發送的是跨域,需在配置文件中config/index.js進行設置。后臺如果是Tp5則定義一個資源路由。 js中使用import進來,然后.get或.post。返回在.then函數中如果成功,失敗則是在.catch函數中
1.不要在模板里面寫過多表達式
2.循環調用子組件時添加key
3.頻繁切換的使用v-show,不頻繁切換的使用v-if
4.盡量少用float,可以用flex
5.按需加載,可以用require或者import()按需加載需要的組件
6.路由懶加載
extend 是構造一個組件的語法器。 然后這個組件你可以作用到Vue.component這個全局注冊方法里 還可以在任意vue模板里使用組件。 也可以作用到vue實例或者某個組件中的components屬性中并在內部使用apple組件。 Vue.component 你可以創建 ,也可以取組件。
png24位的圖片在iE6瀏覽器上出現背景 解決方案是做成PNG8.也可以引用一段腳本處理.
瀏覽器默認的margin和padding不同。 解決方案是加一個全局的*{margin:0;padding:0;}來統一。
IE6雙邊距bug:塊屬性標簽float后,又有橫行的margin情況下,在ie6顯示margin比設置的大。
浮動ie產生的雙倍距離(IE6雙邊距問題:在IE6下,如果對元素設置了浮動,同時又設置了margin-left或margin-right,margin值會加倍。) #box{ float:left; width:10px; margin:0 0 0 100px;}
復制代碼 => 相同點:
1.數據驅動頁面,提供響應式的試圖組件
2.都有virtual DOM,組件化的開發,通過props參數進行父子之間組件傳遞數據,都實現了webComponents規范
3.數據流動單向,都支持服務器的渲染SSR
4.都有支持native的方法,react有React native, vue有wexx
=> 不同點:
1.數據綁定:Vue實現了雙向的數據綁定,react數據流動是單向的
2.數據渲染:大規模的數據渲染,react更快
3.使用場景:React配合Redux架構適合大規模多人協作復雜項目,Vue適合小快的項目
4.開發風格:react推薦做法jsx + inline style把html和css都寫在js了
vue是采用webpack + vue-loader單文件組件格式,html, js, css同一個文件
復制代碼Redux數據流里,reduces其實是根據之前的狀態(previous state)和現有的action(current action)
更新state(這個state可以理解為上下累加器的結果)
每次redux reducer被執行時,state和action被傳入,這個state根據action進行累加或者是'自身消減'(reduce),
進而返回最新的state,這也就是典型reduce函數的用法:state -> action -> state
復制代碼refs就想一個逃生窗,允許我們之間訪問dom元素或者組件實例,可以向組件添加一個ref屬性的值是一個回調函數,
它將接受地城dom元素或組件的已掛在實例,作為第一個參數
復制代碼幫組我們跟蹤哪些項目已更改、添加、從列表中刪除,key是獨一無二的,可以讓我們高效的去定位元素,并且操作它
復制代碼三個狀態:Mounting(已插入真實的DOM)
Updating(正在被重新渲染)
Unmounting(已移除真實的DOM)
componentDIdMount 在第一次渲染后調用,只在客服端。之后組件已經生成對應的DOM結構,
componentDidUpdate 在組件完成更新后立即調用,在出初始化是不會調用
復制代碼父組件通過props 給子組件傳遞數據,子組件則是通過調用父組件傳給它的函數給父組件傳遞數據。
復制代碼虛擬DOM相當于在js和真實dom中間加了一個緩存,利用dom diff算法避免了沒有必要的doom操作,從而提高性能
具體實現步驟:
·用JavaScript對象結構表示DOM樹的結構;然后用這個樹構建一個真正的DOM樹,插到文檔中
·當狀態變更的時候,重新構造一棵樹的對象樹,然后用新的樹和舊的樹進行對比,記錄兩棵樹差異
·把2所記錄的差異應用到步驟1所構建的真正的DOM樹上,試圖就更新了。
復制代碼1.把樹形結構按照層級分解,只比較同級元素
2.給列表結構的每個單元添加key屬性,方便比較。在實際代碼中,會對新舊兩棵樹進行一個深度優先的遍歷,這樣每個節點都會有一個標記
3.在深度優先遍歷的時候,每遍歷到一個節點就把該節點和新的樹進行對比。如果有差異的話就記錄到一個對象里面
Vritual DOM 算法主要實現上面步驟的三個函數:element, diff, patch。然后就可以實際的進行使用
react只會匹配相同的class的component(這里的class指的是組件的名字)
合并操作,條用component的setState方法的時候,React將其標記為dirty.到每一個時間循環借宿,React檢查所有標記dirty的component重新繪制
4.選擇性子樹渲染。可以重寫shouldComponentUpdate提高diff的性能
復制代碼flux的最大特點,就是數據的‘單向流動’
1.用戶訪問View
2.View發出用戶的Action
3.Dispatcher收到Action,要求state進行相應的更新
4.store更新后,發出一個‘change’事件后,更新頁面
復制代碼shouldComponentUpdate 這個方法用來判斷是否需要調用render方法重新描繪dom.因為dom的描繪非常消耗性能,
如果我們在shouldComponentUpdate方法中能夠寫出更優化的dom diff算法,可以極大的提高性能
復制代碼根據組件的職責通常把組件分為UI組件和容器組件
UI組件負責UI的呈現,容器組件負責管理數據和邏輯
兩者通過React-redux提供connect方法聯系起來
復制代碼setState通過一個隊列機制實現state更新,當執行setState時,會將需要更新的state很后放入狀態隊列
而不會立即更新this.state,隊列機制可以高效地批量更新state。如果不通過setState而直接修改this.state的值
那么該state將不會被放入狀態隊列中。當下次調用setState并對狀態隊列進行合并時,就會忽略之前修改的state,造成不可預知的錯誤
同時,也利用了隊列機制實現了setState的異步更新,避免了頻繁的重復更新state
同步更新state:
setState 函數并不會阻塞等待狀態更新完畢,因此 setNetworkActivityIndicatorVisible 有可能先于數據渲染完畢就執行。
第二個參數是一個回調函數,在setState的異步操作結束并且組件已經重新渲染的時候執行
也就是說,我們可以通過這個回調來拿到更新的state的值,實現代碼的同步
例子:componentDidMount() {
fetch('https://test.com')
.then((res) => res.json())
.then(
(data) => {
this.setState({ data:data });
StatusBar.setNetworkActivityIndicatorVisible(false);
}
復制代碼1.異步加載模塊
2.提取第三庫
3.代碼壓縮
4.去除不必要的插件
復制代碼一、減少代碼體積 1.使用CommonsChunksPlugin 提取多個chunk之間的通用模塊,減少總體代碼體積
2.把部分依賴轉移到CDN上,避免每次編譯過程都由Webpack處理
3.對一些組件庫采用按需加載,避免無用的代碼
二、減少目錄檢索范圍
·在使用loader的時候,通過制定exclude和include選項,減少loader遍歷的目錄范圍,從而加快webpack編譯速度
三、減少檢索路經:resolve.alias可以配置webpack模塊解析的別名,對于比較深的解析路經,可以對其配置alias
復制代碼 1、首屏加載和按需加載,懶加載
2、資源預加載
3、圖片壓縮處理,使用base64內嵌圖片
4、合理緩存dom對象
5、使用touchstart代替click(click 300毫秒的延遲)
6、利用transform:translateZ(0),開啟硬件GUP加速
7、不濫用web字體,不濫用float(布局計算消耗性能),減少font-size聲明
8、使用viewport固定屏幕渲染,加速頁面渲染內容
9、盡量使用事件代理,避免直接事件綁定
復制代碼1.減少入口文件體積
2.靜態資源本地緩存
3.開啟Gzip壓縮
4.使用SSR,nuxt.js
復制代碼由來:
300毫米延遲解決的是雙擊縮放。雙擊縮放,手指在屏幕快速點擊兩次。safari瀏覽器就會將網頁縮放值原始比例。由于用戶可以雙擊縮放或者是滾動的操作,
當用戶點擊屏幕一次之后,瀏覽器并不會判斷用戶確實要打開至這個鏈接,還是想要進行雙擊操作
因此,safair瀏覽器就會等待300ms,用來判斷用戶是否在次點擊了屏幕
解決方案:1.禁用縮放,設置meta標簽 user-scalable=no
2.fastclick.js
原理:FastClick的實現原理是在檢查到touchend事件的時候,會通過dom自定義事件立即
發出click事件,并把瀏覽器在300ms之后真正的click事件阻止掉
fastclick.js還可以解決穿透問題
在不改變外部行為的前提下,簡化結構、添加可讀性
復制代碼 2XX(成功處理了請求狀態)
200 服務器已經成功處理請求,并提供了請求的網頁
201 用戶新建或修改數據成功
202 一個請求已經進入后臺
204 用戶刪除成功
3XX(每次請求使用的重定向不要超過5次)
304 網頁上次請求沒有更新,節省帶寬和開銷
4XX(表示請求可能出錯,妨礙了服務器的處理)
400 服務器不理解請求的語法
401 用戶沒有權限(用戶名,密碼輸入錯誤)
403 用戶得到授權(401相反),但是訪問被禁止
404 服務器找不到請求的網頁,
5XX(表示服務器在處理請求的時候發生內部錯誤)
500 服務器遇到錯誤,無法完成請求
503 服務器目前無法使用(超載或停機維護)
復制代碼1.服務器首先產生Etag,服務器可在稍后使用它來判斷頁面是否被修改。本質上,客戶端通過該記號傳回服務器要求服務器驗證(客戶端)緩存)
2.304是 HTTP的狀態碼,服務器用來標識這個文件沒有被修改,不返回內容,瀏覽器接受到這個狀態碼會去去找瀏覽器緩存的文件
3.流程:客戶端請求一個頁面A。服務器返回頁面A,并在A上加一個Tage客服端渲染該頁面,并把Tage也存儲在緩存中。客戶端再次請求頁面A
并將上次請求的資源和ETage一起傳遞給服務器。服務器檢查Tage.并且判斷出該頁面自上次客戶端請求之后未被修改。直接返回304
last-modified: 客服端請求資源,同時有一個last-modified的屬性標記此文件在服務器最后修改的時間
客服端第二次請求此url時,根據http協議。瀏覽器會向服務器發送一個If-Modified-Since報頭,
詢問該事件之后文件是否被修改,沒修改返回304
有了Last-Modified,為什么還要用ETag?
1、因為如果在一秒鐘之內對一個文件進行兩次更改,Last-Modified就會不正確(Last—Modified不能識別秒單位的修改)
2、某些服務器不能精確的得到文件的最后修改時間
3、一些文件也行會周期新的更改,但是他的內容并不改變(僅僅改變修改的事件),這個時候我們并不希望客戶端認為文件被修改,而重新Get
ETag,為什么還要用Last-Modified?
1、兩者互補,ETag的判斷的缺陷,比如一些圖片等靜態文件的修改
2、如果每次掃描內容都生成ETag比較,顯然要比直接比較修改時間慢的多。
ETag是被請求變量的實體值(文件的索引節,大小和最后修改的時間的Hash值)
1、ETag的值服務器端對文件的索引節,大小和最后的修改的事件進行Hash后得到的。
復制代碼1.get數據是存放在url之后,以?分割url和傳輸數據,參數之間以&相連; post方法是把提交的數據放在http包的Body中
2.get提交的數據大小有限制,(因為瀏覽器對url的長度有限制),post的方法提交的數據沒有限制
3.get需要request.queryString來獲取變量的值,而post方式通過request.from來獲取變量的值
4.get的方法提交數據,會帶來安全問題,比如登錄一個頁面,通過get的方式提交數據,用戶名和密碼就會出現在url上
復制代碼1.超文本的傳輸協議,是用于從萬維網服務器超文本傳輸到本地資源的傳輸協議
2.基于TCP/IP通信協議來傳遞數據(HTML,圖片資源)
3.基于運用層的面向對象的協議,由于其簡潔、快速的方法、適用于分布式超媒體信息系統
4.http請求信息request:
請求行(request line)、請求頭部(header),空行和請求數據四部分構成
請求行,用來說明請求類型,要訪問的資源以及所使用的HTTP版本.
請求頭部,用來說明服務器要使用的附加信息
空行,請求頭部后面的空行是必須的
請求數據也叫主體,可以添加任意的其他數據。
5.http相應信息Response
狀態行、消息報頭、空行和響應正文
狀態行,由HTTP協議版本號, 狀態碼, 狀態消息 三部分組成
消息報頭,用來說明客戶端要使用的一些附加信息
空行,消息報頭后面的空行是必須的
響應正文,服務器返回給客戶端的文本信息。
復制代碼https:是以安全為目標的HTTP通道,簡單講是HTTP的安全版本,通過SSL加密
http:超文本傳輸協議。是一個客服端和服務器端請求和應答的標準(tcp),使瀏覽器更加高效,使網絡傳輸減少
復制代碼長連接:HTTP1.0需要使用keep-alive參數來告知服務器建立一個長連接,而HTP1.1默認支持長連接
節約寬帶:HTTP1.1支持只發送一個header信息(不帶任何body信息)
host域(設置虛擬站點,也就是說,web server上的多個虛擬站點可以共享同一個ip端口):HTTP1.0沒有host域
1.http2采用的二進制文本傳輸數據,而非http1文本格式,二進制在協議的解析和擴展更好
2.數據壓縮:對信息頭采用了HPACK進行壓縮傳輸,節省了信息頭帶來的網絡流量
3.多路復用:一個連接可以并發處理多個請求
4.服務器推送:我們對支持HTTP2.0的web server請求數據的時候,服務器會順便把一些客戶端需要的資源一起推送到客戶端,免得客戶端再次創建連接發送請求到服務器端獲取。這種方式非常合適加載靜態資源
復制代碼1.web緩存就是存在于客戶端與服務器之間的一個副本、當你第一個發出請求后,緩存根據請求保存輸出內容的副本
2.緩存的好處
(1)減少不必要的請求
(2)降低服務器的壓力,減少服務器的消耗
(3)降低網絡延遲,加快頁面打開速度(直接讀取瀏覽器的數據)
復制代碼1.sql注入原理:是將sql代碼偽裝到輸入參數中,傳遞到服務器解析并執行的一種攻擊手法。也就是說,
在一些對server端發起的請求參數中植入一些sql代碼,server端在執行sql操作時,會拼接對應參數,
同時也將一些sql注入攻擊的“sql”拼接起來,導致會執行一些預期之外的操作。
防范:1.對用戶輸入進行校驗
2.不適用動態拼接sql
2.XSS(跨站腳本攻擊):往web頁面插入惡意的html標簽或者js代碼。
舉例子:在論壇放置一個看是安全的鏈接,竊取cookie中的用戶信息
防范:1.盡量采用post而不使用get提交表單
2.避免cookie中泄漏用戶的隱式
3.CSRF(跨站請求偽裝):通過偽裝來自受信任用戶的請求
舉例子:黃軼老師的webapp音樂請求數據就是利用CSRF跨站請求偽裝來獲取QQ音樂的數據
防范:在客服端頁面增加偽隨機數,通過驗證碼
XSS和CSRF的區別:
1.XSS是獲取信息,不需要提前知道其他用戶頁面的代碼和數據包
2.CSRF代替用戶完成指定的動作,需要知道其他頁面的代碼和數據包
復制代碼1.盡可能的避開互聯網有可能影響數據傳輸速度和穩定性的瓶頸和環節。使內容傳輸的更快更穩定。
2.關鍵技術:內容存儲和分發技術中
3.基本原理:廣泛采用各種緩存服務器,將這些緩存服務器分布到用戶訪問相對的地區或者網絡中。當用戶訪問網絡時利用全局負載技術
將用戶的訪問指向距離最近的緩存服務器,由緩存服務器直接相應用戶的請求(全局負載技術)
復制代碼客服端發c起請求連接服務器端s確認,服務器端也發起連接確認客服端確認。
第一次握手:客服端發送一個請求連接,服務器端只能確認自己可以接受客服端發送的報文段
第二次握手: 服務端向客服端發送一個鏈接,確認客服端收到自己發送的報文段
第三次握手: 服務器端確認客服端收到了自己發送的報文段
復制代碼1.查詢NDS(域名解析),獲取域名對應的IP地址 查詢瀏覽器緩存
2.瀏覽器與服務器建立tcp鏈接(三次握手)
3.瀏覽器向服務器發送http請求(請求和傳輸數據)
4.服務器接受到這個請求后,根據路經參數,經過后端的一些處理生成html代碼返回給瀏覽器
5.瀏覽器拿到完整的html頁面代碼開始解析和渲染,如果遇到外部的css或者js,圖片一樣的步驟
6.瀏覽器根據拿到的資源對頁面進行渲染,把一個完整的頁面呈現出來
復制代碼流程:解析html以及構建dom樹 -> 構建render樹 -> 布局render樹 -> 繪制render樹
概念:1.構建DOM樹: 渲染引擎解析HTML文檔,首先將標簽轉換成DOM樹中的DOM node(包括js生成的標簽)生成內容樹
2.構建渲染樹: 解析對應的css樣式文件信息(包括js生成的樣式和外部的css)
3.布局渲染樹:從根節點遞歸調用,計算每一個元素的大小,位置等。給出每個節點所在的屏幕的精準位置
4.繪制渲染樹:遍歷渲染樹,使用UI后端層來繪制每一個節點
重繪:當盒子的位置、大小以及其他屬性,例如顏色、字體大小等到確定下來之后,瀏覽器便把這些顏色都按照各自的特性繪制一遍,將內容呈現在頁面上
觸發重繪的條件:改變元素外觀屬性。如:color,background-color等
重繪是指一個元素外觀的改變所觸發的瀏覽器行為,瀏覽器會根據元素的新屬性重新繪制,使元素呈現新的外觀
注意:table及其內部元素需要多次計算才能確定好其在渲染樹中節點的屬性值,比同等元素要多發時間,要盡量避免使用table布局
重排(重構/回流/reflow): 當渲染書中的一部分(或全部)因為元素的規模尺寸,布局,隱藏等改變而需要重新構建,這就是回流。
每個頁面都需要一次回流,就是頁面第一次渲染的時候
重排一定會影響重繪,但是重繪不一定會影響重排
復制代碼1.瀏覽器預先加載css后,可以不必等待HTML加載完畢就可以渲染頁面了
2.其實HTML渲染并不會等到完全加載完在渲染頁面,而是一邊解析DOM一邊渲染。
3.js寫在尾部,主要是因為js主要扮演事件處理的功能,一方面很多操作是在頁面渲染后才執行的。另一方面可以節省加載時間,使頁面能夠更加的加載,提高用戶的良好體驗
但是隨著JS技術的發展,JS也開始承擔頁面渲染的工作。比如我們的UI其實可以分被對待,把渲染頁面的js放在前面,時間處理的js放在后面
復制代碼1.indexBD: 是h5的本地存儲庫,把一些數據存儲到瀏覽器中,沒網絡,瀏覽器可以從這里讀取數據,離線運用。5m
2.Cookie: 通過瀏覽器記錄信息確認用戶身份,最大4kb,這也就限制了傳輸的數據,請求的性能會受到影響
3.Session: 服務器端使用的一種記錄客戶狀態的機制(session_id存在set_cookie發送到客服端,保存為cookie)
4.localStroage: h5的本地存儲,數據永久保存在客服端
1、cookie,sessionStorage,localStorage是存放在客戶端,session對象數據是存放在服務器上 實際上瀏覽器和服務器之間僅需傳遞session id即可,服務器根據session-id找到對應的用戶session對象 session存儲數據更安全一些,一般存放用戶信息,瀏覽器只適合存儲一般的數據 2、cookie數據始終在同源的http請求中攜帶,在瀏覽器和服務器來回傳遞,里面存放著session-id sessionStorage,localStorage僅在本地保存 3、大小限制區別,cookie數據不超過4kb,localStorage在谷歌瀏覽中2.6MB 4、數據有效期不同,cookie在設置的(服務器設置)有效期內有效,不管窗口和瀏覽器關閉 sessionStorage僅在當前瀏覽器窗口關閉前有效,關閉即銷毀(臨時存儲) localStorage始終有效
SessionStorage和localStorage區別: 1.sessionStorage用于本地存儲一個會話(session)中的數據,這些數據只有在用一個會話的頁面中才能被訪問(也就是說在第一次通信過程中) 并且在會話結束后數據也隨之銷毀,不是一個持久的本地存儲,會話級別的儲存 2.localStorage用于持久化的本地存儲,除非主動刪除數據,否則不會過期
復制代碼1、token就是令牌,比如你授權(登錄)一個程序時,他就是個依據,判斷你是否已經授權該軟件(最好的身份認證,安全性好,且是唯一的)
用戶身份的驗證方式
2、cookie是寫在客戶端一個txt文件,里面包括登錄信息之類的,這樣你下次在登錄某個網站,就會自動調用cookie自動登錄用戶名
服務器生成,發送到瀏覽器、瀏覽器保存,下次請求再次發送給服務器(存放著登錄信息)
3、session是一類用來客戶端和服務器之間保存狀態的解決方案,會話完成被銷毀(代表的就是服務器和客戶端的一次會話過程)
cookie中存放著sessionID,請求會發送這個id。sesion因為request對象而產生。
復制代碼 1、用戶通過用戶名和密碼發送請求
2、服務器端驗證
3、服務器端返回一個帶簽名的token,給客戶端
4、客戶端儲存token,并且每次用于發送請求
5、服務器驗證token并且返回數據
每一次請求都需要token
復制代碼 1、cookie數據存放在客戶的瀏覽器上,session數據放在服務器上。
2、cookie不是很安全,別人可以分析存放在本地的COOKIE并進行COOKIE欺騙考慮到安全應當使用session。
3、session會在一定時間內保存在服務器上。當訪問增多,會比較占用你服務器的性能考慮到減輕服務器性能方面,應當使用COOKIE。
4、單個cookie保存的數據不能超過4K,很多瀏覽器都限制一個站點最多保存20個cookie。
復制代碼 1、session認證只是把簡單的User的信息存儲Session里面,sessionID不可預測,一種認證手段。只存在服務端,不能共享到其他的網站和第三方App
2、token是oAuth Token,提供的是認證和授權,認證針對用戶,授權是針對App,目的就是讓某APP有權訪問某用戶的的信息。Token是唯一的,
token不能轉移到其他的App,也不能轉到其他用戶上。(適用于App)
3、session的狀態是存在服務器端的,客戶端只存在session id, Token狀態是存儲在客戶端的
復制代碼 1、數量和長度的限制。每個特定的域名下最多生成20個cookie(chorme和safari沒有限制)
2、安全性問題。
一、觀察者模式:juejin.cn/post/684490… juejin.cn/post/684490… 在軟件開發設計中是一個對象(subject),維護一系列依賴他的對象(observer),當任何狀態發生改變自動通知他們。強依賴關系 簡單理解:數據發生改變時,對應的處理函數就會自動執行。一個Subjet,用來維護Observers,為某些event來通知(notify)觀察者
二、發布-訂閱者 有一個信息中介,過濾 耦合性低 它定義了一種一對多的關系,可以使多個觀察者對象對一個主題對象進行監聽,當這個主題對象發生改變時,依賴的所有對象都會被通知到。
復制代碼1.冒泡排序:重復走訪過要排序的數列,一次比較兩個元素,如果他們的順序錯誤就把它們交換過來。
實現過程:1.比較相鄰的元素。如果第一個比第二個大,就交換他們兩個
2.對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對,這樣在最后的元素應該會是最大的數
3.針對所有的元素重復以上的步驟,除了最后一個
4.重復步驟1-3,直到排序完成。
2.選擇排序:首先在未排序序列中找到最小值,放在排序序列的起始位置,然后,在從剩下未排序元素中繼續尋找最小值,然后放在與排序序列的末尾
實現過程:
3.插入排序:構建有序序列,對于未排序數據,在已排序序列中沖后向前掃描,找到相應位置并插入
實現過程:1.從第一個元素開始,該元素可以認為已經被排序
2.取出下一個元素,在已排序的元素序列中沖后向前掃描
3.如果該元素(以排序)大于新元素,將元素向后移一位
4.在取出一個元素,比較之前的,直到找到自己合適的位置
4.桶排序:將數據分布到有限數量的桶里,每個桶在分別排序
1.快速排序:快速排序使用分治法把一個串(list)分為兩個子串(sub-lists).具體算法實現
實現過程:1.從數組中挑出一個元素,成為一個基準
2.重新排列數組,所有元素比基準小的擺在基準前面,所有元素比基準大的擺在基準后面(相同的可以擺在一邊)
這個分區退出之后,該基準就處于數列的中間位置。成為分區操作。
3.遞歸的把小于基準值的子數列和大于基準值元素的子數列排序
算法實現: function quickSort (arr) {
if (arr.length <= 1) {return arr}
var destIndex = Math.floor(arr.length/2)
var left = [], right = [];
var dest = arr.splice(destIndex,1)[0];
for (var i =0;i<arr.length;i++){
if (arr[i]<dest) {
left.push(arr[i])
} else {
right.push(arr[i]) }
return quickSort(left).concat([dest],quickSort(right)
2.堆排序:利用對這種數據結構所涉及的一種排序算法,堆積是一個近乎完全二叉樹的結構,并同時滿足堆積的性質:即子節點的鍵值或索引總是小于(或大于)它的父節點。
實現過程:1.
復制代碼1.雙重循環
2.indexOf
3.數組排序去重 最快你Olong
復制代碼判斷回文字符串:(遞歸的思想)
1.字符串分隔,倒轉,聚合[...obj].reverse().join('')
2.字符串頭部和尾部,逐次向中間檢測
實現:function isPalindrome(line) {
line += '';
for (var i=0,j=line.length-1;i<j;i++,j--) {
if (line.chartAt(i) !== line.chartAt(j) {
return false
}
3.遞歸
復制代碼 二分查找可以解決已排序數組的查找問題,即只要數組中包含T(要查找的值),那么通過不斷的縮小包含T的數據范圍,就可以最終要找到的數
(1) 一開始,數據范圍覆蓋整個數組。
(2) 將數組的中間項與T進行比較,如果T比數組的中間項小,則到數組的前半部分繼續查找,反之,則到數組的后半部分繼續查找。
(3) 就這樣,每次查找都可以排除一半元素,相當于范圍縮小一半。這樣反復比較,反復縮小范圍,最終會在數組中找到T
代碼實現:function binarySearch (data, dest, start, end){
var end = end || data.length-1;
var start = start || 0;
var m = Math.floor((start+end)/2);
if (dest<data[m]){
return binarySearch(data, dest, 0, m-1)
} else {
return binarySearch(data, dest, m+1, end)
}}
return false
復制代碼一句話概括:1.bind()返回一個新函數,并不會立即執行。
2.bind的第一個參數將作為他運行時的this,之后的一系列參數將會在傳遞的實參前傳入作為他的參數
3.bind返回函數作為構造函數,就是可以new的,bind時指定的this值就會消失,但傳入的參數依然生效
復制代碼Function.prototype.bind = function (obj, arg) {
var arg = Array.prototype.slice.call(arguments, 1);
var context = this;
var bound = function (newArg) {
arg = arg.concat(Array.prototype.slice.call(newArg);
return context.apply(obj, arg)
}
var F = function () {} // 在new一個bind會生成新函數,必須的條件就是要繼承原函數的原型,因此用到寄生繼承來完成我們的過程
F.prototype = context.prototype;
bound.prototype = new F();
return bound;
}
復制代碼ajax的原理:相當于在用戶和服務器之間加一個中間層(ajax引擎),使用戶操作與服務器響應異步化。
優點:在不刷新整個頁面的前提下與服務器通信維護數據。不會導致頁面的重載
可以把前端服務器的任務轉嫁到客服端來處理,減輕服務器負擔,節省寬帶
劣勢:不支持back。對搜索引擎的支持比較弱;不容易調試
怎么解決呢?通過location.hash值來解決Ajax過程中導致的瀏覽器前進后退按鍵失效,
解決以前被人常遇到的重復加載的問題。主要比較前后的hash值,看其是否相等,在判斷是否觸發ajax
復制代碼function getData(url) {
var xhr = new XMLHttpRequest(); // 創建一個對象,創建一個異步調用的對象
xhr.open('get', url, true) // 設置一個http請求,設置請求的方式,url以及驗證身份
xhr.send() //發送一個http請求
xhr.onreadystatechange = function () { //設置一個http請求狀態的函數
if (xhr.readyState == 4 && xhr.status ==200) {
console.log(xhr.responseText) // 獲取異步調用返回的數據
}
}
}
Promise(getData(url)).resolve(data => data)
AJAX狀態碼:0 - (未初始化)還沒有調用send()方法
1 - (載入)已調用send方法,正在發送請求
2 - (載入完成呢)send()方法執行完成
3 - (交互)正在解析相應內容
4 - (完成)響應內容解析完成,可以在客戶端調用了
```
#### 三、函數節流(throttle)
```
function throttle (func, wait) {
var timeout;
var previous = 0;
return function () {
context = this;
args = arguments;
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context,args)
}, wait);
}
}
}
}
```
#### 四、函數防抖(dobounce)
```
function debounce (func, wait) {
var timeout;
return function() {
var context = this;
var args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context,args)
}, wait);
}
}
```
#### 五、實現一個函數clone,可以對JavaScript中的5種主要的數據類型(包括Number、String、Object、Array、Boolean)進行值復制
```
Object.prototype.clone = function() {
var newObject = this.constructor === Array ? [] : {} //對象的深拷貝 獲取對應的構造函數 [] 或者 {}
for (let e in this) { //遍歷對象的屬性 in this[e]
newObject[e] = typeof this[e] === 'object' ? this[e].clone() : this[e] //對象中的屬性如果還是對象 那就繼續遞歸 否則就返回基本的數據類型
}
return newObject
}
```
#### 六、實現一個簡單的Promise https://juejin.cn/post/6844903625769091079
```
class Promise {
constructor (executor) { // executor里面有兩個參數,一個叫resolve(成功),一個叫reject(失敗)。
this.status = 'pending',
this.value = undefined;
this.reason = undefined;
// 成功存放的數組
this.onResolvedCallbacks = [];
// 失敗存放法數組
this.onRejectedCallbacks = [];
let resolve = (value) => {
if (this.status == 'pending') {
this.status = 'resolve';
this.value = value;
this.onResolvedCallbacks.forEach(fn => fn())
}
}
let reject = (reason) => {
if (this.status == 'pending') {
this.status = 'reject';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn())
}
}
try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then (onFullFilled,onRejected) {
if (this.status == 'resolved') {
onFullFilled(this.value)
}
if (this.status == 'rejectd') {
onRejected(this.reason);
}
if (this.status == 'pending') {
this.onResolvedCallbacks.push(()=>{
onFullFilled(this.value);
})
this.onRejectedCallbacks.push(()=> {
onRejected(this.reason);
})
}
}
}
const p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello world')
}, 1000);
})
p.then((data) =>{
console.log(data)
},(err) =>{
console.log(err);
})
```
#### 七、發布訂閱者模式(觀察者模式)
```
var event = {}; // 發布者
event.clientList = [] //發布者的緩存列表
event.listen = function (fn) { // 增加訂閱者函數
this.clientList.push(fn)
}
event.trigger = function () { // 發布信息
for (var i =0;i<this.clientList.length;i++) {
var fn = this.clientList[i];
fn.apply(this, arguments);
}
}
event.listen (function(time) {
console.log('正式上班時間為:' +time)
})
event.trigger ('2018/7')
```
#### 八、手動寫一個node服務器
```
const http = require('http');
const fs = require('fs');
const server = http.createServer((req,res) => {
if (reu.url == '/') {
const indexFile = fs.createReadStream('./index.html')
req.writeHead(200,{'context-Type':'text/html;charset = utf8})
indexFile.pipe(res)
}
server.listen(8080)
```
如果本文對你有幫助,就留個關注點個贊支持下吧,你的「關注」和「贊」是我創作的動力。
原文鏈接:https://juejin.cn/post/6844903976693940231
*請認真填寫需求信息,我們會在24小時內與您取得聯系。