整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          如何讓網頁“看起來”展現地更快?骨架屏二三事

          網頁展現的更快,官方說法叫做首屏繪制,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。
          • 骨架屏為了盡快展現,要求快速和簡單,所以骨架屏多數使用靜態的圖片。而且把圖片編譯成 base64 編碼格式可以節省網絡請求,使得骨架屏更快展現,更加有效。


          <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>
          


          • 在執行 JS 開始真正內容的渲染之前,清空骨架屏 HTML
          • 以 Vue 為例,即在 mount 之前清空內容即可。


          let app = new Vue({...})
          let container = document.querySelector('#app')
          if (container) {
           container.innerHTML = ''
          }
          app.$mount(container)
          


          僅此兩步,并不牽涉多么復雜的機制和高端的 API,因此非常容易應用,趕快用起來!

          示例

          我編寫了一個示例,用于快速展現骨架屏的效果,代碼在此。

          • index.html
          • 默認包含了骨架屏,并且內聯了樣式(以 <style> 標簽添加在頭部)。
          • render.js
          • 它負責創建 DOM 元素并添加到 <body> 上,渲染頁面實際的內容,用來模擬常見的前端渲染模式。
          • index.css
          • 頁面實際內容的樣式表,不包含骨架屏的樣式。


          代碼的三個文件各司其職,配合上面的實現思路,應該還是很好理解的。可以在 這里 查看效果。

          因為這個示例的邏輯太過簡單,而實際的前端渲染框架復雜得多,包含的功能也不單純是渲染,還有狀態管理,路由管理,虛擬 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/34550387- xiaop 同學原作
          • Loading CSS without blocking render https://keithclark.co.uk/articles/loading-css-without-blocking-render/- 使用修改 media 的方式達成目的。
          • filamentgroup/loadCSS https://github.com/filamentgroup/loadCSS - 同樣使用修改 rel 的方式,并提供了 preload polyfill
          轉自作者作者:小蘑菇小哥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。
          • 骨架屏為了盡快展現,要求快速和簡單,所以骨架屏多數使用靜態的圖片。而且把圖片編譯成 base64 編碼格式可以節省網絡請求,使得骨架屏更快展現,更加有效。


          <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>
          


          • 在執行 JS 開始真正內容的渲染之前,清空骨架屏 HTML
          • 以 Vue 為例,即在 mount 之前清空內容即可。


          let app = new Vue({...})
          let container = document.querySelector('#app')
          if (container) {
           container.innerHTML = ''
          }
          app.$mount(container)
          


          僅此兩步,并不牽涉多么復雜的機制和高端的 API,因此非常容易應用,趕快用起來!

          示例

          我編寫了一個示例,用于快速展現骨架屏的效果,代碼在此。

          • index.html
          • 默認包含了骨架屏,并且內聯了樣式(以 <style> 標簽添加在頭部)。
          • render.js
          • 它負責創建 DOM 元素并添加到 <body> 上,渲染頁面實際的內容,用來模擬常見的前端渲染模式。
          • index.css
          • 頁面實際內容的樣式表,不包含骨架屏的樣式。


          代碼的三個文件各司其職,配合上面的實現思路,應該還是很好理解的。可以在 這里 查看效果。

          因為這個示例的邏輯太過簡單,而實際的前端渲染框架復雜得多,包含的功能也不單純是渲染,還有狀態管理,路由管理,虛擬 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/34550387- xiaop 同學原作
          • Loading CSS without blocking render https://keithclark.co.uk/articles/loading-css-without-blocking-render/- 使用修改 media 的方式達成目的。
          • filamentgroup/loadCSS https://github.com/filamentgroup/loadCSS - 同樣使用修改 rel 的方式,并提供了 preload polyfill

          金九銀十跳槽季,offer快到碗里來,前端面試考點眾多,而網上各個知識點的博客文章又太多,看的眼花繚亂...所以我整理了一下常見知識點的精華文章,每個知識點的文章控制在3篇以內,盡量覆蓋該知識點的下容易被面試到的所有內容,文章都是之前自己讀過的,確定是精華干貨。文章會一直更新,也歡迎大家推薦精華文章,大家共同學習進步呀!


          一、CSS問題

          1.flex布局

          display:flex; 在父元素設置,子元素受彈性盒影響,默認排成一行,如果超出一行,按比例壓縮 flex:1; 子元素設置,設置子元素如何分配父元素的空間,flex:1,子元素寬度占滿整個父元素align-items:center 定義子元素在父容器中的對齊方式,center 垂直居中justify-content:center 設置子元素在父元素中居中,前提是子元素沒有把父元素占滿,讓子元素水平居中。

          2.css3的新特性

          transtion transition-property 規定設置過渡效果的 CSS 屬性的名稱。

          transition-duration 規定完成過渡效果需要多少秒或毫秒。

          transition-timing-function 規定速度效果的速度曲線。

          transition-delay 定義過渡效果何時開始。

          animation屬性可以像Flash制作動畫一樣,通過控制關鍵幀來控制動畫的每一步,實現更為復雜的動畫效果。

          ainimation實現動畫效果主要由兩部分組成:

          通過類似Flash動畫中的幀來聲明一個動畫;

          在animation屬性中調用關鍵幀聲明的動畫。

          translate 3D建模效果

          3.img中alt和title的區別

          圖片中的 alt屬性是在圖片不能正常顯示時出現的文本提示。alt有利于SEO優化

          圖片中的 title屬性是在鼠標在移動到元素上的文本提示。

          4.用純CSS創建一個三角形

          復制代碼 <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>
          

          5.如何理解CSS的盒子模型?

          標準盒子模型:寬度=內容的寬度(content)+ border + padding

          低版本IE盒子模型:寬度=內容寬度(content+border+padding)

          6.如何讓一個div水平居中

          已知寬度,block元素 ,添加添加margin:0 auto屬性。

          已知寬度,絕對定位的居中 ,上下左右都為0,margin:auto

          7.如何讓一個div水平垂直居中

          復制代碼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; /* 方便看效果 */
          }  
          

          8.如何清除浮動?

          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
          }
          

          9.css3實現三欄布局,左右固定,中間自適應

          圣杯布局/雙飛翼布局

          復制代碼 <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>
          

          10.display:none 和 visibility: hidden的區別

          display:none 隱藏對應的元素,在文檔布局中不再給它分配空間,它各邊的元素會合攏,就當他從來不存在。

          visibility:hidden 隱藏對應的元素,但是在文檔布局中仍保留原來的空間。

          11.CSS中 link 和@import 的區別是?

          link屬于HTML標簽,而@import是CSS提供的頁面被加載的時,link會同時被加載,而@import引用的CSS會等到頁面被加載完再加載

          import只在IE5以上才能識別,而link是HTML標簽,無兼容問題

          link方式的樣式的權重 高于@import的權重.

          12.position的absolute與fixed共同點與不同點

          共同點: 改變行內元素的呈現方式,display被置為block 讓元素脫離普通流,不占據空間 默認會覆蓋到非定位元素上

          不同點: absolute的”根元素“是可以設置的 fixed的”根元素“固定為瀏覽器窗口。當你滾動網頁,fixed元素與瀏覽器窗口之間的距離是不變的。

          13..transition和animation的區別

          Animation和transition大部分屬性是相同的,他們都是隨時間改變元素的屬性值,他們的主要區別是transition需要觸發一個事件才能改變屬性, 而animation不需要觸發任何事件的情況下才會隨時間改變屬性值,并且transition為2幀,從from .... to,而animation可以一幀一幀的。

          transition 規定動畫的名字 規定完成過渡效果需要多少秒或毫秒 規定速度效果 定義過渡效果何時開始 animation 指定要綁定到選擇器的關鍵幀的名稱

          14.CSS優先級

          復制代碼不同級別:總結排序:!important > 行內樣式>ID選擇器 > 類選擇器 > 標簽 > 通配符 > 繼承 > 瀏覽器默認屬性
          	1.屬性后面加!import 會覆蓋頁面內任何位置定義的元素樣式
          	2.作為style屬性寫在元素內的樣式
          	3.id選擇器
          	4.類選擇器
          	5.標簽選擇器
          	6.通配符選擇器(*)
          	7.瀏覽器自定義或繼承
          **同一級別:后寫的會覆蓋先寫的**
          

          css選擇器的解析原則:選擇器定位DOM元素是從右往左的方向,這樣可以盡早的過濾掉一些不必要的樣式規則和元素

          15.雪碧圖:

          復制代碼        多個圖片集成在一個圖片中的圖
          	使用雪碧圖可以減少網絡請求的次數,加快允許的速度
          	通過background-position,去定位圖片在屏幕的哪個位置
          

          二、JS問題

          1.typeof和instance of 檢測數據類型有什么區別?

          相同點: 都常用來判斷一個變量是否為空,或者是什么類型的。

          不同點: typeof 返回值是一個字符串,用來說明變量的數據類型 instanceof 用于判斷一個變量是否屬于某個對象的實例.

          16.使元素消失的方法

          復制代碼visibility:hidden、display:none、z-index=-1、opacity:0
          1.opacity:0,該元素隱藏起來了,但不會改變頁面布局,并且,如果該元素已經綁定了一些事件,如click事件也能觸發
          2.visibility:hidden,該元素隱藏起來了,但不會改變頁面布局,但是不會觸發該元素已經綁定的事件
          3.display:node, 把元素隱藏起來,并且會改變頁面布局,可以理解成在頁面中把該元素刪掉
          

          .談一談深克隆和淺克隆?

          淺克隆: 只是拷貝了基本類型的數據,而引用類型數據,復制后也是會發生引用,我們把這種拷貝叫做“(淺復制)淺拷貝”,換句話說,淺復制僅僅是指向被復制的內存地址,如果原地址中對象被改變了,那么淺復制出來的對象也會相應改變。

          深克隆: 創建一個新對象,屬性中引用的其他對象也會被克隆,不再指向原有對象地址。 JSON.parse、JSON.stringify()

          3.es6的新特性都有哪些?

          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數據結構,數組去重

          4.==和===區別是什么?

          =賦值

          ==返回一個布爾值;相等返回true,不相等返回false; 允許不同數據類型之間的比較; 如果是不同類型的數據進行,會默認進行數據類型之間的轉換; 如果是對象數據類型的比較,比較的是空間地址

          === 只要數據類型不一樣,就返回false;

          5.常見的設計模式有哪些?

          復制代碼1、js工廠模式
          2、js構造函數模式
          3、js原型模式
          4、構造函數+原型的js混合模式
          5、構造函數+原型的動態原型模式
          6、觀察者模式
          7、發布訂閱模式
          

          6.call bind apply 的區別?

          call() 和apply()的第一個參數相同,就是指定的對象。這個對象就是該函數的執行上下文。

          call()和apply()的區別就在于,兩者之間的參數。

          call()在第一個參數之后的 后續所有參數就是傳入該函數的值。

          apply() 只有兩個參數,第一個是對象,第二個是數組,這個數組就是該函數的參數。 bind() 方法和前兩者不同在于: bind() 方法會返回執行上下文被改變的函數而不會立即執行,而前兩者是 直接執行該函數。他的參數和call()相同。

          7.js繼承方式有哪些?

          原型鏈繼承 核心: 將父類的實例作為子類的原型

          構造繼承 核心:使用父類的構造函數來增強子類實例,等于是復制父類的實例屬性給子類

          實例繼承 核心:為父類實例添加新特性,作為子類實例返回

          拷貝繼承

          組合繼承 核心:通過調用父類構造,繼承父類的屬性并保留傳參的優點,然后通過將父類實例作為子類原型,實現 函數復用

          寄生組合繼承 核心:通過寄生方式,砍掉父類的實例屬性,這樣,在調用兩次父類的構造的時候,就不會初始化兩次實 例方法/屬性,避免的組合繼承的缺點

          8.你怎樣看待閉包?

          個人感覺,簡單來說閉包就是在函數里面聲明函數,本質上說就是在函數內部和函數外部搭建起一座橋梁,使得子函數可以訪問父函數中所有的局部變量,但是反之不可以,這只是閉包的作用之一,另一個作用,則是保護變量不受外界污染,使其一直存在內存中,在工作中我們還是少使用閉包的好,因為閉包太消耗內存,不到萬不得已的時候盡量不使用。

          9.你是如何理解原型和原型鏈的?

          把所有的對象共用的屬性全部放在堆內存的一個對象(共用屬性組成的對象),然后讓每一個對象的 __proto__存儲這個「共用屬性組成的對象」的地址。而這個共用屬性就是原型,原型出現的目的就是為了減少不必要的內存消耗。而原型鏈就是對象通過__proto__向當前實例所屬類的原型上查找屬性或方法的機制,如果找到Object的原型上還是沒有找到想要的屬性或者是方法則查找結束,最終會返回undefined

          10.瀏覽器渲染的主要流程是什么?

          將html代碼按照深度優先遍歷來生成DOM樹。 css文件下載完后也會進行渲染,生成相應的CSSOM。 當所有的css文件下載完且所有的CSSOM構建結束后,就會和DOM一起生成Render Tree。 接下來,瀏覽器就會進入Layout環節,將所有的節點位置計算出來。 最后,通過Painting環節將所有的節點內容呈現到屏幕上。

          11.從輸入url地址到頁面相應都發生了什么?

          復制代碼1、瀏覽器的地址欄輸入URL并按下回車。
          2、瀏覽器查找當前URL是否存在緩存,并比較緩存是否過期。3、DNS解析URL對應的IP。
          4、根據IP建立TCP連接(三次握手)。
          5、HTTP發起請求。
          6、服務器處理請求,瀏覽器接收HTTP響應。
          7、渲染頁面,構建DOM樹。
          8、關閉TCP連接(四次揮手)
          

          12.session、cookie、localStorage的區別

          相同點 都是保存在瀏覽器端,且同源的。

          不同點

          • cookie數據始終在同源的http請求中攜帶,即cookie在瀏覽器和服務器間來回傳遞。
          • 而sessionStorage和localStorage不會自動把數據發給服務器,僅在本地保存。
          • cookie數據還有路徑(path)的概念,可以限制cookie只屬于某個路徑下。 存儲大小限制也不同,cookie數據不能超過4k,同時因為每次http請求都會攜帶cookie,所以cookie只適合保存很小的數據。
          • sessionStorage和localStorage 雖然也有存儲大小的限制,但比cookie大得多,可以達到5M或更大。 數據有效期不同,sessionStorage:僅在當前瀏覽器窗口關閉前有效,自然也就不可能持久保持;
          • localStorage:始終有效,窗口或瀏覽器關閉也一直保存,因此用作持久數據;
          • cookie只在設置的cookie過期時間之前一直有效,即使窗口或瀏覽器關閉。 作用域不同,sessionStorage不在不同的瀏覽器窗口中共享,即使是同一個頁面;
          • localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。

          13.js中跨域方法

          同源策略(協議+端口號+域名要相同)

          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()

          14.前端有哪些頁面優化方法?

          • 減少 HTTP請求數
          • 從設計實現層面簡化頁面
          • 合理設置 HTTP緩存
          • 資源合并與壓縮
          • 合并 CSS圖片,減少請求數的又一個好辦法。
          • 將外部腳本置底(將腳本內容在頁面信息內容加載后再加載)
          • 多圖片網頁使用圖片懶加載。
          • 在js中盡量減少閉包的使用
          • 盡量合并css和js文件
          • 盡量使用字體圖標或者SVG圖標,來代替傳統的PNG等格式的圖片
          • 減少對DOM的操作
          • 在JS中避免“嵌套循環”和 “死循環”
          • 盡可能使用事件委托(事件代理)來處理事件綁定的操作

          15.Ajax的四個步驟

          1.創建ajax實例

          2.執行open 確定要訪問的鏈接 以及同步異步

          3.監聽請求狀態

          4.發送請求

          16.數組去重的方法

          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;
          }
          

          17.ajax中get和post請求的區別

          • get 一般用于獲取數據
          • get請求如果需要傳遞參數,那么會默認將參數拼接到url的后面;然后發送給服務器;
          • get請求傳遞參數大小是有限制的;是瀏覽器的地址欄有大小限制;
          • get安全性較低
          • get 一般會走緩存,為了防止走緩存,給url后面每次拼的參數不同;放在?后面,一般用個時間戳
          • post 一般用于發送數據
          • post傳遞參數,需要把參數放進請求體中,發送給服務器;
          • post請求參數放進了請求體中,對大小沒有要求;
          • post安全性比較高;
          • post請求不會走緩存;

          18.ajax的狀態碼

          2開頭

          • 200 : 代表請求成功;

          3開頭

          • 301 : 永久重定向;
          • 302: 臨時轉移
          • 304 : 讀取緩存 [表示瀏覽器端有緩存,并且服務端未更新,不再向服務端請求資源]
          • 307:臨時重定向

          以4開頭的都是客戶端的問題;

          • 400 :數據/格式錯誤
          • 401: 權限不夠;(身份不合格,訪問網站的時候,登錄和不登錄是不一樣的)
          • 404 : 路徑錯誤,找不到文件

          以5開頭都是服務端的問題

          • 500 : 服務器的問題
          • 503: 超負荷;

          19.移動端的兼容問題

          • 給移動端添加點擊事件會有300S的延遲 如果用點擊事件,需要引一個fastclick.js文件,解決300s的延遲 一般在移動端用ontouchstart、ontouchmove、ontouchend
          • 移動端點透問題,touchstart 早于 touchend 早于click,click的觸發是有延遲的,這個時間大概在300ms左右,也就是說我們tap觸發之后蒙層隱藏, 此時 click還沒有觸發,300ms之后由于蒙層隱藏,我們的click觸發到了下面的a鏈接上 盡量都使用touch事件來替換click事件。例如用touchend事件(推薦)。 用fastclick,github.com/ftlabs/fast… 用preventDefault阻止a標簽的click 消除 IE10 里面的那個叉號 input:-ms-clear{display:none;}
          • 設置緩存 手機頁面通常在第一次加載后會進行緩存,然后每次刷新會使用緩存而不是去重新向服務器發送請求。如果不希望使用緩存可以設置no-cache。
          • 圓角BUG 某些Android手機圓角失效 background-clip: padding-box; 防止手機中網頁放大和縮小 這點是最基本的,做為手機網站開發者來說應該都知道的,就是設置meta中的viewport
          • 設置用戶截止縮放,一般寫視口的時候就已經寫好了。

          20.JS中同步和異步,以及js的事件流

          同步:在同一時間內做一件事情

          異步:在同一時間內做多個事情 JS是單線程的,每次只能做一件事情,JS運行在瀏覽器中,瀏覽器是多線程的,可以在同一時間執行多個任務。

          21.JS中常見的異步任務

          定時器、ajax、事件綁定、回調函數、async await、promise

          22.TCP的三次握手和四次揮手

          三次握手

          • 第一次握手:客戶端發送一個SYN碼給服務器,要求建立數據連接;
          • 第二次握手: 服務器SYN和自己處理一個SYN(標志);叫SYN+ACK(確認包);發送給客戶端,可以建立連接
          • 第三次握手: 客戶端再次發送ACK向服務器,服務器驗證ACK沒有問題,則建立起連接;

          四次揮手

          • 第一次揮手: 客戶端發送FIN(結束)報文,通知服務器數據已經傳輸完畢;
          • 第二次揮手: 服務器接收到之后,通知客戶端我收到了SYN,發送ACK(確認)給客戶端,數據還沒有傳輸完成
          • 第三次揮手: 服務器已經傳輸完畢,再次發送FIN通知客戶端,數據已經傳輸完畢
          • 第四次揮手: 客戶端再次發送ACK,進入TIME_WAIT狀態;服務器和客戶端關閉連接;

          23.為什么建立連接是三次握手,而斷開連接是四次揮手呢?

          建立連接的時候, 服務器在LISTEN狀態下,收到建立連接請求的SYN報文后,把ACK和SYN放在一個報文里發送給客戶端。 而關閉連接時,服務器收到對方的FIN報文時,僅僅表示對方不再發送數據了但是還能接收數據,而自己也未必全部數據都發送給對方了,所以己方可以立即關閉,也可以發送一些數據給對方后,再發送FIN報文給對方來表示同意現在關閉連接,因此,己方ACK和FIN一般都會分開發送,從而導致多了一次。

          24.DOM diff原理

          如果元素類型發生變化,直接替換 如果是文本,則比較文本里面的內容,是否有差異,如果是元素就需要比較當前元素的屬性是否相等,會先比較key, 在比較類型 為什么 react中循環 建議不要使用索引 ,如果純為了展示 那可以使用索引

          25.作用域

          全局作用域

          • 瀏覽器打開一個頁面時,瀏覽器會給JS代碼提供一個全局的運行環境,那么這個環境就是全局作用域 一個頁面只有一個全局作用域,全局作用域下有一個window對象 window是全局作用域下的最大的一個內置對象(全局作用域下定義的變量和函數都會存儲在window下) 如果是全局變量,都會給window新增一個鍵值對;屬性名就是變量名,屬性值就是變量所存儲的值 如果變量只被var過,那么存儲值是undefined 在私有作用域中是可以獲取到全局變量的,但是在全局作用域中不能獲取私有變量

          私有作用域

          • 函數執行會形成一個新的私有的作用域(執行多次,形成多個私有作用域) 私有作用域在全局作用域中形成,具有包含的關系; 在一個全局作用域中,可以有很多個私有作用域 在私有作用域下定義的變量都是私有變量 形參也是私有變量 函數體中通過function定義的函數也是私有的,在全局作用域不能使用;

          塊級作用域

          • es6中新引入的一種作用域 在js中常見到的if{}、for{}、while{}、try{}、catch{}、switch case{}都是塊級作用域 var obj = {} //對象的大括號不是塊級作用域 塊級作用域中的同一變量不能被重復聲明(塊級下var和function不能重名,否則會報錯) 作用域鏈

          上級作用域

          • 函數在哪里定義,他的上一級作用域就是哪,和函數在哪個作用域下執行沒有關系 作用域鏈:當獲取變量所對應的值時,首先看變量是否是私有變量,如果不是私有變量,要繼續向上一級作用域中查找,如果上一級也沒有,那么會繼續向上一級查找,直到找到全局作用域為止;如果全局作用域也沒有,則會報錯;這樣一級一級向上查找,就會形成作用域鏈 當前作用域沒有的,則會繼續向上一級作用域查找 當前函數的上一級作用域跟函數在哪個作用域下執行沒有關系,只跟函數在哪定義有關(重點)

          26.Promise處理異步

          他是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徹底完成

          27.map和forEach的區別

          相同點

          • 都是循環遍歷數組中的每一項 forEach和map方法里每次執行匿名函數都支持3個參數,參數分別是item(當前每一項)、index(索引值)、arr(原數組),需要用哪個的時候就寫哪個 匿名函數中的this都是指向window 只能遍歷數組

          不同點

          • map方法返回一個新的數組,數組中的元素為原始數組調用函數處理后的值。(原數組進行處理之后對應的一個新的數組。) map()方法不會改變原始數組 map()方法不會對空數組進行檢測 forEach()方法用于調用數組的每個元素,將元素傳給回調函數.(沒有return,返回值是undefined)

          注意:forEach對于空數組是不會調用回調函數的。

          28.async await函數

          async/await函數是異步代碼的新方式

          async/await是基于promise實現的

          async/await使異步代碼更像同步代碼

          await 只能在async函數中使用,不能再普通函數中使用,要成對出現

          默認返回一個promise實例,不能被改變

          await下面的代碼是異步,后面的代碼是同步的

          29.this指向

          全局作用域下的this指向window 如果給元素的事件行為綁定函數,那么函數中的this指向當前被綁定的那個元素 函數中的this,要看函數執行前有沒有 . , 有 . 的話,點前面是誰,this就指向誰,如果沒有點,指向window 自執行函數中的this永遠指向window 定時器中函數的this指向window 構造函數中的this指向當前的實例 call、apply、bind可以改變函數的this指向 箭頭函數中沒有this,如果輸出this,就會輸出箭頭函數定義時所在的作用域中的this

          30.原型

          所有的函數數據類型都天生自帶一個prototype屬性,該屬性的屬性值是一個對象 prototype的屬性值中天生自帶一個constructor屬性,其constructor屬性值指向當前原型所屬的類 所有的對象數據類型,都天生自帶一個_proto_屬性,該屬性的屬性值指向當前實例所屬類的原型

          31.異步回調(如何解決回調地獄)

          復制代碼promise、generator、async/await
          
          promise: 1.是一個對象,用來傳遞異步操作的信息。代表著某個未來才會知道結果的時間,并未這個事件提供統一的api,供進異步處理
          	  2.有了這個對象,就可以讓異步操作以同步的操作的流程來表達出來,避免層層嵌套的回調地獄
          	  3.promise代表一個異步狀態,有三個狀態pending(進行中),Resolve(以完成),Reject(失敗)
          	  4.一旦狀態改變,就不會在變。任何時候都可以得到結果。從進行中變為以完成或者失敗
          		promise.all() 里面狀態都改變,那就會輸出,得到一個數組
          		promise.race() 里面只有一個狀態變為rejected或者fulfilled即輸出
          		promis.finally()不管指定不管Promise對象最后狀態如何,都會執行的操作(本質上還是then方法的特例)
          

          32.前端事件流

          復制代碼事件流描述的是從頁面中接受事件的順序,事件 捕獲階段 處于目標階段 事件冒泡階段 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
              }
            }
          

          33.事件如何先捕獲后冒泡?

          在DOM標準事件模型中,是先捕獲后冒泡。但是如果要實現先冒泡后捕獲的效果, 對于同一個事件,監聽捕獲和冒泡,分別對應相應的處理函數,監聽到捕獲事件,先暫緩執行,直到冒泡事件被捕獲后再執行捕獲事件。

          • 哪些事件不支持冒泡事件:鼠標事件:mouserleave mouseenter 焦點事件:blur focus UI事件:scroll resize

          34. 如何判斷一個變量是對象還是數組(prototype.toString.call())。

          復制代碼千萬不要使用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()方法測試一個對象是否存在另一個對象的原型鏈上。

          35.setTimeout 和 setInterval的機制

          復制代碼因為js是單線程的。瀏覽器遇到etTimeout 和 setInterval會先執行完當前的代碼塊,在此之前會把定時器推入瀏覽器的
          待執行時間隊列里面,等到瀏覽器執行完當前代碼之后會看下事件隊列里有沒有任務,有的話才執行定時器里的代碼
          

          36.splice和slice、map和forEach、 filter()、reduce()的區別

          復制代碼 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問題

          1.聊聊對vue的理解

          vue是一個漸進式的JS框架。他易用,靈活,高效; 可以把一個頁面分隔成多個組件;當其他頁面有類似功能時,直接讓封裝的組件進行復用; 他是構建用戶界面的聲明式框架,只關心圖層;不關心具體是如何實現的

          2.V-model的原理是什么?

          Vue的雙向數據綁定是由數據劫持結合發布者訂閱者實現的。 數據劫持是通過Object.defineProperty()來劫持對象數據的setter和getter操作。 在數據變動時作你想做的事

          原理 通過Observer來監聽自己的model數據變化,通過Compile來解析編譯模板指令,最終利用Watcher搭起Observer和Compile之間的通信橋梁,達到數據變化->視圖更新 在初始化vue實例時,遍歷data這個對象,給每一個鍵值對利用Object.definedProperty對data的鍵值對新增get和set方法,利用了事件監聽DOM的機制,讓視圖去改變數據

          3.談談對生命周期的理解

          • beforeCreate階段:vue實例的掛載元素el和數據對象data都是undefined,還沒有初始化。
          • created階段:vue實例的數據對象data有了,可以訪問里面的數據和方法,未掛載到DOM,el還沒有
          • beforeMount階段:vue實例的el和data都初始化了,但是掛載之前為虛擬的dom節點
          • mounted階段:vue實例掛載到真實DOM上,就可以通過DOM獲取DOM節點
          • beforeUpdate階段:響應式數據更新時調用,發生在虛擬DOM打補丁之前,適合在更新之前訪問現有的DOM,比如手動移除已添加的事件監聽器
          • updated階段:虛擬DOM重新渲染和打補丁之后調用,組成新的DOM已經更新,避免在這個鉤子函數中操作數據,防止死循環
          • beforeDestroy階段:實例銷毀前調用,實例還可以用,this能獲取到實例,常用于銷毀定時器,解綁事件
          • destroyed階段:實例銷毀后調用,調用后所有事件監聽器會被移除,所有的子實例都會被銷毀

          4.VUE和REACT有什么區別?

          react整體是函數式的思想,把組件設計成純組件,狀態和邏輯通過參數傳入,所以在react中,是單向數據流;

          vue的思想是響應式的,也就是基于是數據可變的,通過對每一個屬性建立Watcher來監聽,當屬性變化的時候,響應式的更新對應的虛擬dom。

          5.vuex的流程

          復制代碼頁面通過mapAction異步提交事件到action。action通過commit把對應參數同步提交到mutation。
          mutation會修改state中對于的值。 最后通過getter把對應值跑出去,在頁面的計算屬性中
          通過mapGetter來動態獲取state中的值
          

          6.vuex有哪幾種狀態和屬性

          state中保存著共有數據,數據是響應式的 getter可以對state進行計算操作,主要用來過濾一些數據,可以在多組件之間復用 mutations定義的方法動態修改state中的數據,通過commit提交方法,方法必須是同步的 actions將mutations里面處理數據的方法變成異步的,就是異步操作數據,通store.dispatch來分發actions,把異步的方法寫在actions中,通過commit提交mutations,進行修改數據。 modules:模塊化vuex

          7.vue路由的兩種模式

          hash ——即地址欄URL中的#符號(此hsah 不是密碼學里的散列運算) hash 雖然出現URL中,但不會被包含在HTTP請求中,對后端完全沒有影響,因此改變hash不會重新加載頁面。 history ——利用了HTML5 History Interface 中新增的pushState() 和replaceState() 方法

          這兩個方法應用于瀏覽器的歷史記錄站,在當前已有的back、forward、go 的基礎之上,它們提供了對歷史記錄進行修改的功能。只是當它們執行修改是,雖然改變了當前的URL,但你瀏覽器不會立即向后端發送請求。

          8.vue中 key 值的作用

          當 Vue.js 用v-for正在更新已渲染過的元素列表時,它默認用“就地復用”策略。 如果數據項的順序被改變,Vue 將不會移動 DOM 元素來匹配數據項的順序,而是簡單復用此處每個元素,并且確保它在特定索引下顯示已被渲染過的每個元素。

          key的作用主要是為了高效的更新虛擬DOM。

          9$route和$router的區別

          $route是“路由信息對象”,包括path,params,hash,query,fullPath,matched,name等路由信息參數。 $router是“路由實例”對象包括了路由的跳轉方法,鉤子函數等。

          10.vue-router守衛

          導航守衛 router.beforeEach 全局前置守衛

          • to: Route: 即將要進入的目標(路由對象)
          • from: Route: 當前導航正要離開的路由
          • next: Function: 一定要調用該方法來 resolve 這個鉤子。(一定要用這個函數才能去到下一個路由,如果不用就攔截) 執行效果依賴 next 方法的調用參數。
          • next(): 進行管道中的下一個鉤子。如果全部鉤子執行完了,則導航的狀態就是 confirmed (確認的)。
          • next(false): 取消進入路由,url地址重置為from路由地址(也就是將要離開的路由地址)。
          復制代碼// 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銷毀,防止離開之后,定時器還在調用。
            }
          }
          

          11.axios是什么?怎么使用?描述使用它實現登錄功能的流程?

          請求后臺資源的模塊。

          復制代碼$ npm install axios -S裝好
          

          然后發送的是跨域,需在配置文件中config/index.js進行設置。后臺如果是Tp5則定義一個資源路由。 js中使用import進來,然后.get或.post。返回在.then函數中如果成功,失敗則是在.catch函數中

          12.vue修飾符

          • stop:阻止事件的冒泡
          • prevent:阻止事件的默認行為
          • once:只觸發一次
          • self:只觸發自己的事件行為時,才會執行

          13.vue項目中的性能優化

          1.不要在模板里面寫過多表達式

          2.循環調用子組件時添加key

          3.頻繁切換的使用v-show,不頻繁切換的使用v-if

          4.盡量少用float,可以用flex

          5.按需加載,可以用require或者import()按需加載需要的組件

          6.路由懶加載

          14.vue.extend和vue.component

          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;}

          React問題

          1.react和vue的區別

          復制代碼   =>  相同點:
          	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同一個文件
          

          2.redux中的reducer(純函數)

          復制代碼Redux數據流里,reduces其實是根據之前的狀態(previous state)和現有的action(current action)
          更新state(這個state可以理解為上下累加器的結果)
          每次redux reducer被執行時,state和action被傳入,這個state根據action進行累加或者是'自身消減'(reduce),
          進而返回最新的state,這也就是典型reduce函數的用法:state ->  action ->  state
          

          3.react的refs

          復制代碼refs就想一個逃生窗,允許我們之間訪問dom元素或者組件實例,可以向組件添加一個ref屬性的值是一個回調函數,
          它將接受地城dom元素或組件的已掛在實例,作為第一個參數
          

          4.react中的keys

          復制代碼幫組我們跟蹤哪些項目已更改、添加、從列表中刪除,key是獨一無二的,可以讓我們高效的去定位元素,并且操作它
          

          5.React的生命周期

          復制代碼三個狀態:Mounting(已插入真實的DOM)
          	  Updating(正在被重新渲染)
          	  Unmounting(已移除真實的DOM)
          componentDIdMount 在第一次渲染后調用,只在客服端。之后組件已經生成對應的DOM結構,
          componentDidUpdate 在組件完成更新后立即調用,在出初始化是不會調用
          

          6.React子組件向父組件傳值

          復制代碼父組件通過props 給子組件傳遞數據,子組件則是通過調用父組件傳給它的函數給父組件傳遞數據。
          

          7.為什么虛擬DOM會提高性能 www.zhihu.com/question/29…

          復制代碼虛擬DOM相當于在js和真實dom中間加了一個緩存,利用dom diff算法避免了沒有必要的doom操作,從而提高性能
          具體實現步驟:
          	·用JavaScript對象結構表示DOM樹的結構;然后用這個樹構建一個真正的DOM樹,插到文檔中
                  ·當狀態變更的時候,重新構造一棵樹的對象樹,然后用新的樹和舊的樹進行對比,記錄兩棵樹差異
          	·把2所記錄的差異應用到步驟1所構建的真正的DOM樹上,試圖就更新了。
          

          8.diff算法

          復制代碼1.把樹形結構按照層級分解,只比較同級元素
          2.給列表結構的每個單元添加key屬性,方便比較。在實際代碼中,會對新舊兩棵樹進行一個深度優先的遍歷,這樣每個節點都會有一個標記
          3.在深度優先遍歷的時候,每遍歷到一個節點就把該節點和新的樹進行對比。如果有差異的話就記錄到一個對象里面
          Vritual DOM 算法主要實現上面步驟的三個函數:element, diff, patch。然后就可以實際的進行使用
          react只會匹配相同的class的component(這里的class指的是組件的名字)
          合并操作,條用component的setState方法的時候,React將其標記為dirty.到每一個時間循環借宿,React檢查所有標記dirty的component重新繪制
          4.選擇性子樹渲染。可以重寫shouldComponentUpdate提高diff的性能	
          

          9.簡述下flux的思想

          復制代碼flux的最大特點,就是數據的‘單向流動’
          1.用戶訪問View
          2.View發出用戶的Action
          3.Dispatcher收到Action,要求state進行相應的更新
          4.store更新后,發出一個‘change’事件后,更新頁面
          

          10.reac性能優化是哪個周期函

          復制代碼shouldComponentUpdate 這個方法用來判斷是否需要調用render方法重新描繪dom.因為dom的描繪非常消耗性能,
          如果我們在shouldComponentUpdate方法中能夠寫出更優化的dom diff算法,可以極大的提高性能
          

          11.react怎么劃分業務組件和技術組件

          復制代碼根據組件的職責通常把組件分為UI組件和容器組件
          UI組件負責UI的呈現,容器組件負責管理數據和邏輯
          兩者通過React-redux提供connect方法聯系起來
          

          12.setState

          復制代碼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);
                  }
          

          性能優化

          一、webpack打包文件體積過大?(最終打包為一個js文件)

          復制代碼1.異步加載模塊
          2.提取第三庫
          3.代碼壓縮
          4.去除不必要的插件
          

          二、如何優化webpack構建的性能

          復制代碼一、減少代碼體積 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、盡量使用事件代理,避免直接事件綁定
          

          四、Vue的SPA 如何優化加載速度

          復制代碼1.減少入口文件體積
          2.靜態資源本地緩存
          3.開啟Gzip壓縮
          4.使用SSR,nuxt.js
          

          五、移動端300ms延遲

          復制代碼由來:
          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 服務器目前無法使用(超載或停機維護)     
          

          二、304的緩存原理(添加Etag標簽.last-modified) 304 網頁上次請求沒有更新,節省帶寬和開銷

          復制代碼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后得到的。
          

          三、get/post的區別

          復制代碼1.get數據是存放在url之后,以?分割url和傳輸數據,參數之間以&相連; post方法是把提交的數據放在http包的Body中
          2.get提交的數據大小有限制,(因為瀏覽器對url的長度有限制),post的方法提交的數據沒有限制
          3.get需要request.queryString來獲取變量的值,而post方式通過request.from來獲取變量的值
          4.get的方法提交數據,會帶來安全問題,比如登錄一個頁面,通過get的方式提交數據,用戶名和密碼就會出現在url上
          

          四、http協議的理解

          復制代碼1.超文本的傳輸協議,是用于從萬維網服務器超文本傳輸到本地資源的傳輸協議
          2.基于TCP/IP通信協議來傳遞數據(HTML,圖片資源)
          3.基于運用層的面向對象的協議,由于其簡潔、快速的方法、適用于分布式超媒體信息系統
          4.http請求信息request:
          	請求行(request line)、請求頭部(header),空行和請求數據四部分構成
          
          	請求行,用來說明請求類型,要訪問的資源以及所使用的HTTP版本.
          	請求頭部,用來說明服務器要使用的附加信息
          	空行,請求頭部后面的空行是必須的
          	請求數據也叫主體,可以添加任意的其他數據。
          5.http相應信息Response
          	狀態行、消息報頭、空行和響應正文
          
          	狀態行,由HTTP協議版本號, 狀態碼, 狀態消息 三部分組成
          	消息報頭,用來說明客戶端要使用的一些附加信息
          	空行,消息報頭后面的空行是必須的
          	響應正文,服務器返回給客戶端的文本信息。
          

          五、http和https

          復制代碼https:是以安全為目標的HTTP通道,簡單講是HTTP的安全版本,通過SSL加密
          http:超文本傳輸協議。是一個客服端和服務器端請求和應答的標準(tcp),使瀏覽器更加高效,使網絡傳輸減少
          

          六、http1.0 1.1 2.0的區別

          復制代碼長連接: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請求數據的時候,服務器會順便把一些客戶端需要的資源一起推送到客戶端,免得客戶端再次創建連接發送請求到服務器端獲取。這種方式非常合適加載靜態資源
          

          七、web緩存

          復制代碼1.web緩存就是存在于客戶端與服務器之間的一個副本、當你第一個發出請求后,緩存根據請求保存輸出內容的副本
          2.緩存的好處
                  (1)減少不必要的請求
              (2)降低服務器的壓力,減少服務器的消耗
              (3)降低網絡延遲,加快頁面打開速度(直接讀取瀏覽器的數據)
          

          八、常見的web安全及防護原理

          復制代碼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代替用戶完成指定的動作,需要知道其他頁面的代碼和數據包
          

          九、CDN(內容分發網絡)

          復制代碼1.盡可能的避開互聯網有可能影響數據傳輸速度和穩定性的瓶頸和環節。使內容傳輸的更快更穩定。
          2.關鍵技術:內容存儲和分發技術中
          3.基本原理:廣泛采用各種緩存服務器,將這些緩存服務器分布到用戶訪問相對的地區或者網絡中。當用戶訪問網絡時利用全局負載技術
          	    將用戶的訪問指向距離最近的緩存服務器,由緩存服務器直接相應用戶的請求(全局負載技術)
          

          十、TCP三次握手 (客服端和服務器端都需要確認各自可收發)

          復制代碼客服端發c起請求連接服務器端s確認,服務器端也發起連接確認客服端確認。
          第一次握手:客服端發送一個請求連接,服務器端只能確認自己可以接受客服端發送的報文段
          第二次握手: 服務端向客服端發送一個鏈接,確認客服端收到自己發送的報文段
          第三次握手: 服務器端確認客服端收到了自己發送的報文段
          

          十一、從輸入url到獲取頁面的完整過程 blog.csdn.net/samjustin1/…

          復制代碼1.查詢NDS(域名解析),獲取域名對應的IP地址  查詢瀏覽器緩存
          2.瀏覽器與服務器建立tcp鏈接(三次握手)
          3.瀏覽器向服務器發送http請求(請求和傳輸數據)
          4.服務器接受到這個請求后,根據路經參數,經過后端的一些處理生成html代碼返回給瀏覽器
          5.瀏覽器拿到完整的html頁面代碼開始解析和渲染,如果遇到外部的css或者js,圖片一樣的步驟
          6.瀏覽器根據拿到的資源對頁面進行渲染,把一個完整的頁面呈現出來
          

          十二、瀏覽器渲染原理及流程 DOM -> CSSOM -> render -> layout -> print

          復制代碼流程:解析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): 當渲染書中的一部分(或全部)因為元素的規模尺寸,布局,隱藏等改變而需要重新構建,這就是回流。
          	每個頁面都需要一次回流,就是頁面第一次渲染的時候
          
          重排一定會影響重繪,但是重繪不一定會影響重排
          

          十三、為什么css放在頂部而js寫在后面

          復制代碼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的本地存儲,數據永久保存在客服端
          

          cookie,sessionStorage,localStorage

          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用于持久化的本地存儲,除非主動刪除數據,否則不會過期

          token、cookie、session三者的理解???!!!

          復制代碼1、token就是令牌,比如你授權(登錄)一個程序時,他就是個依據,判斷你是否已經授權該軟件(最好的身份認證,安全性好,且是唯一的)
              用戶身份的驗證方式    
          
          2、cookie是寫在客戶端一個txt文件,里面包括登錄信息之類的,這樣你下次在登錄某個網站,就會自動調用cookie自動登錄用戶名
              服務器生成,發送到瀏覽器、瀏覽器保存,下次請求再次發送給服務器(存放著登錄信息)
          
          3、session是一類用來客戶端和服務器之間保存狀態的解決方案,會話完成被銷毀(代表的就是服務器和客戶端的一次會話過程)
              cookie中存放著sessionID,請求會發送這個id。sesion因為request對象而產生。
          

          基于Token的身份驗證:(最簡單的token: uid用戶唯一的身份識別 + time當前事件戳 + sign簽名)

          復制代碼  1、用戶通過用戶名和密碼發送請求
            2、服務器端驗證
            3、服務器端返回一個帶簽名的token,給客戶端
            4、客戶端儲存token,并且每次用于發送請求
            5、服務器驗證token并且返回數據
            每一次請求都需要token
          

          cookie與session區別

          復制代碼  1、cookie數據存放在客戶的瀏覽器上,session數據放在服務器上。
            2、cookie不是很安全,別人可以分析存放在本地的COOKIE并進行COOKIE欺騙考慮到安全應當使用session。
            3、session會在一定時間內保存在服務器上。當訪問增多,會比較占用你服務器的性能考慮到減輕服務器性能方面,應當使用COOKIE。
            4、單個cookie保存的數據不能超過4K,很多瀏覽器都限制一個站點最多保存20個cookie。
          

          session與token區別

          復制代碼  1、session認證只是把簡單的User的信息存儲Session里面,sessionID不可預測,一種認證手段。只存在服務端,不能共享到其他的網站和第三方App
            2、token是oAuth Token,提供的是認證和授權,認證針對用戶,授權是針對App,目的就是讓某APP有權訪問某用戶的的信息。Token是唯一的,
               token不能轉移到其他的App,也不能轉到其他用戶上。(適用于App)
            3、session的狀態是存在服務器端的,客戶端只存在session id, Token狀態是存儲在客戶端的
          

          Cookie的弊端有哪些???(優勢:保存客戶端數據,分擔了服務器存儲的負擔)

          復制代碼  1、數量和長度的限制。每個特定的域名下最多生成20個cookie(chorme和safari沒有限制)
            2、安全性問題。
          

          設計模式

          一、觀察者模式:juejin.cn/post/684490… juejin.cn/post/684490… 在軟件開發設計中是一個對象(subject),維護一系列依賴他的對象(observer),當任何狀態發生改變自動通知他們。強依賴關系 簡單理解:數據發生改變時,對應的處理函數就會自動執行。一個Subjet,用來維護Observers,為某些event來通知(notify)觀察者

          二、發布-訂閱者 有一個信息中介,過濾 耦合性低 它定義了一種一對多的關系,可以使多個觀察者對象對一個主題對象進行監聽,當這個主題對象發生改變時,依賴的所有對象都會被通知到。

          • -兩者的區別: 1.觀察者模式中,觀察者知道Subject ,兩者是相關聯的,而發發布訂閱者只有通過信息代理進行通信 2.在發布訂閱模式中,組件式松散耦合的。正好和觀察者模式相反。 3.觀察者大部分是同步的,比如事件的觸發。Subject就會調用觀察者的方法。而發布訂閱者大多數是異步的() 4.觀察者模式需要在單個應用程序地址空間中實現,而發布訂閱者更像交叉應用模式。

          數據結構和算法

          四、十大排序

          復制代碼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.
          

          五、數組去重 juejin.cn/post/684490…

          復制代碼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
          

          手寫代碼

          一、動手實現一個bind(原理通過apply,call)

          復制代碼一句話概括: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 (異步的javascript和xml)

          復制代碼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


          主站蜘蛛池模板: 日本强伦姧人妻一区二区| 精品人体无码一区二区三区| 久久国产午夜一区二区福利| 精品一区二区三区在线播放视频| 日韩精品一区二区三区影院| 性色A码一区二区三区天美传媒| 亚洲AV午夜福利精品一区二区| 在线精品自拍亚洲第一区| 日本免费电影一区| 无码囯产精品一区二区免费| 日本强伦姧人妻一区二区| 亚洲福利视频一区二区三区| 一区二区三区午夜| 国产成人欧美一区二区三区 | 在线精品国产一区二区| 无码人妻精品一区二区三区在线 | 天堂资源中文最新版在线一区| 在线精品一区二区三区电影| 无码人妻一区二区三区在线视频 | 亚洲AV无码一区二区三区性色| 久久久久人妻一区精品| 亚洲国产精品一区二区久久| 无码人妻精品一区二区三区在线| 亚洲福利精品一区二区三区| 日韩一区二区a片免费观看| 国产成人精品久久一区二区三区| 国产一区二区福利| 少妇精品久久久一区二区三区| 亚洲成AV人片一区二区| 精品一区二区三区中文字幕| 国产亚洲福利精品一区| 日韩精品一区二区三区中文| 97人妻无码一区二区精品免费| 人妻激情偷乱视频一区二区三区| 奇米精品一区二区三区在线观看| 九九久久99综合一区二区| 久久久91精品国产一区二区| 国产精品自拍一区| 视频一区视频二区制服丝袜| 中文字幕一区二区在线播放 | 3D动漫精品啪啪一区二区下载|