banner
 Sayyiku

Sayyiku

Chaos is a ladder
telegram
twitter

題解

001 React / Vue 專案時為什麼要在列表組件中寫 key,其作用是什麼?#

key 的作用就是更新組件時判斷兩個節點是否相同。相同就復用,不相同就重新創建。以避免 “原地復用” 帶來的副作用。

002 函數節流和函數防抖?#

函數節流(throttle)與函數防抖(debounce)核心思想都是通過限制函數調用來實現性能優化,但兩者概念卻有不同:

  • 函數節流:函數按指定間隔調用,限制函數調用頻率

  • 函數防抖:一定時間段連續的函數調用,只讓其執行一次

兩者的使用場景也有不同:

  • 函數節流:頁面滾動事件監聽(scroll)、DOM 元素拖拽(mousemove)、鍵盤事件(keydown)

  • 函數防抖:文本輸入驗證發送請求、窗口縮放(resize)

003 Set、Map、WeakSet 和 WeakMap 的區別?#

  • Set 類似於數組,但是成員的值都是唯一的,可以遍歷。

  • WeakSet 成員都是對象,且都是弱引用,可以用來保存 DOM 節點,不容易造成內存泄漏,不能遍歷。

  • Map 本質上是鍵值對的集合,可以遍歷。

  • WeakMap 只接受對象作為鍵名(null 除外),不接受其他類型的值作為鍵名,鍵名所指向的對象,不能遍歷。

004 Async/Await 如何通過同步的方式實現異步?#

Async/Await 就是一個自執行的 generate 函數,是 Generator 的語法糖。利用 generate 函數的特性把異步的代碼寫成 “同步” 的形式。

Generator 之所以可以通過同步實現異步是它具有暫停執行和恢復執行的特性和函數體內外的數據交換和錯誤處理機制。

005 JS 異步解決方案#

  • 回調函數,缺點:回調地獄,不能用 try catch 捕獲錯誤,不能 return

  • Promise,缺點:無法取消 Promise ,錯誤需要通過回調函數來捕獲

  • Generator,需要配合 co 庫

  • Async/await:代碼清晰,不用像 Promise 寫一大堆 then 鏈,處理了回調地獄的問題

006 簡單講解一下 Http2 的多路復用#

在 HTTP/1 中,每次請求都會建立一次 HTTP 連接,即 3 次握手 4 次揮手,這個過程在一次請求過程中佔用了相當長的時間,即使開啟了 Keep-Alive,解決了多次連接的問題,但是依然有兩個效率上的問題:

  1. 串行的文件傳輸。當請求 a 文件時,b 文件只能等待,等待 a 連接到服務器、服務器處理文件、服務器返回文件,這三個步驟。

  2. 連接數過多。假設服務的設置了最大並發數為 300,由於瀏覽器限制,瀏覽器發起的最大請求數為 6,也就是服務器能承載的最高並發為 50,當第 51 個人訪問時,就需要等待前面某個請求處理完成。

HTTP/2 的多路復用就是為了解決上述的兩個性能問題。在 HTTP/2 中,有兩個非常重要的概念,分別是幀(frame)和流(stream)。幀代表著最小的數據單位,每個幀會標識出該幀屬於哪個流,流也就是多個幀組成的數據流。多路復用,就是在一個 TCP 連接中可以存在多條流,也就是可以發送多個請求,對端可以通過幀中的標識知道屬於哪個請求。通過這個技術,可以避免 HTTP 舊版本中的隊頭阻塞問題,極大地提高傳輸性能。

007 談談你對 TCP 三次握手和四次揮手#

三次握手是保證 client 和 server 均讓對方知道自己的接收和發送能力沒問題的最小次數。

  • 第一次 client => server,只能 server 判斷出 client 具備發送能力

  • 第二次 server => client,client 就可以判斷出 server 具備發送和接受能力

  • 第三次 client => server,雙方均保證了自己的接收和發送能力沒有問題

四次揮手是因為 TCP 是全雙工信道,即客戶端與服務端建立兩條通道:

  • 通道 1:客戶端的輸出連接服務端的輸入

  • 通道 2:客戶端的輸入連接服務端的輸出

兩個通道可以同時工作,所以關閉雙通道時需要四次揮手:

  • 客戶端關閉輸入通道,服務端關閉輸出通道

  • 服務端關閉輸入通道,客戶端關閉輸出通道

008 TCP/IP 協議體系結構四層分別是什麼?#

四層:應用層、傳輸層、網絡層、數據鏈路層。

HTTP、TCP、IP 分別在應用層、傳輸層、網絡層。

009 從 URL 輸入到頁面展現到底發生什麼?#

  1. DNS 解析:將域名解析成 IP 地址

  2. TCP 連接:TCP 三次握手

  3. 發送 HTTP 請求

  4. 服務器處理請求並返回 HTTP 報文

  5. 瀏覽器解析渲染頁面

  6. 斷開連接:TCP 四次揮手

010 介紹 HTTPS 握手過程與中間人攻擊#

HTTPS 握手過程:

  1. 客戶端使用 https 的 url 訪問 web 服務器,要求與服務器建立 ssl 連接

  2. web 服務器收到客戶端請求後,會將網站的證書(包含公鑰)傳送一份給客戶端

  3. 客戶端收到網站證書後會檢查證書的頒發機構以及過期時間,如果沒有問題就隨機產生一個秘鑰

  4. 客戶端利用公鑰將會話秘鑰加密並傳送給服務端,服務端利用自己的私鑰解密出會話秘鑰

  5. 之後服務器與客戶端使用秘鑰加密傳輸

HTTPS 中間人攻擊:

  1. 服務器向客戶端發送公鑰。

  2. 攻擊者截獲公鑰,保留在自己手上。

  3. 然後攻擊者自己生成一個【偽造的】公鑰,發給客戶端。

  4. 客戶端收到偽造的公鑰後,將密鑰加密發給服務器。

  5. 攻擊者獲得加密密鑰,用自己的私鑰解密獲得真秘鑰。

  6. 同時生成假的加密密鑰,發給服務器。

  7. 服務器用私鑰解密獲得假秘鑰。

  8. 服務器用加秘鑰加密傳輸信息。

防範方法:服務端在發送瀏覽器的公鑰中加入 CA 證書,瀏覽器可以驗證 CA 證書的有效性

011 Http 與 Https 有什麼區別?#

Http:超文本傳輸協議,明文傳輸,數據未加密,安全性較差,用的是 80 端口

Https:安全套接字超文本傳輸協議,有 ssl/tsl 證書,數據傳輸過程是加密,安全性較好,用的 443 端口,而且要比較 Http 更耗費服務器資源

012 介紹下重繪和回流(Repaint & Reflow),以及如何進行優化#

瀏覽器渲染機制

瀏覽器采用流式佈局模型。瀏覽器會把 HTML 解析成 DOM,把 CSS 解析成 CSSOM,DOM 和 CSSOM 合併就產生了渲染樹(Render Tree)。有了 RenderTree,就知道了所有節點的樣式,然後計算他們在頁面上的大小和位置,最後把節點繪製到頁面上。

重繪

由於節點的幾何屬性發生改變或者由於樣式發生改變而不會影響佈局的,稱為重繪,例如 outline、visibility、color、background-color 等。

回流

回流是佈局或者幾何屬性需要改變就稱為回流。回流是影響瀏覽器性能的關鍵因素,因為其變化涉及到頁面的佈局更新。一個元素的回流可能會導致了其所有子元素以及緊隨其後的節點、祖先節點元素的回流。

回流必定會發生重繪,重繪不一定會引發回流。

減少重繪與回流方式

CSS

  1. 使用 transform 替代 top/left

  2. 使用 visibility 替換 display: none,因為前者只會引起重繪,後者會引發回流(改變了佈局)

  3. 避免使用 table 佈局,可能很小的一個小改動會造成整個 table 的重新佈局。

  4. 避免設置多層內聯樣式,避免節點層級過多。

  5. 應該儘可能的避免寫過於具體的 CSS 選擇器。

  6. 將動畫效果應用到 position 屬性為 absolute 或 fixed 的元素上,避免影響其他元素的佈局,這樣只是一次重繪,而不是回流。

  7. 避免使用 CSS 表達式,可能會引發回流。

  8. 將頻繁重繪或者回流的節點設置為圖層,圖層能夠阻止該節點的渲染行為影響別的節點,例如 will-change、video、iframe 等標籤,瀏覽器會自動將該節點變為圖層。

  9. CSS3 硬件加速(GPU 加速),可以讓 transform、opacity 這些動畫不會引起回流重繪。

JavaScript

  1. 避免頻繁操作樣式,最好一次性重寫 style 屬性,或者將樣式列表定義為 class 並一次性更改 class 屬性。

  2. 避免頻繁操作 DOM,創建一個 documentFragment,在它上面應用所有 DOM 操作,最後再把它添加到文檔中。

  3. 避免頻繁讀取會引發回流 / 重繪的屬性,如果確實需要多次使用,就用一個變量緩存起來。

  4. 對具有複雜動畫的元素使用絕對定位,使它脫離文檔流,否則會引起父元素及後續元素頻繁回流。

013 opacity: 0、visibility: hidden、display: none 優劣和適用場景#

  • opacity: 0 重建圖層,性能較高

  • visibility: hidden 會造成重繪,比回流性能高一些

  • display: none 會造成回流,性能開銷較大,回流可能會重新計算其所有子元素以及緊隨其後的節點、祖先節點元素的位置、屬性

014 什麼是同源策略?如何解決跨域?#

所謂同源是指 "協議 + 域名 + 端口" 三者相同。

解決跨域的方式:

  1. CORS(跨域資源共享)

  2. Nginx 反向代理

  3. JSONP:JSONP 主要就是利用了 script 標籤沒有跨域限制的這個特性來完成的,僅支持 GET 方法。

  • XSS:跨站腳本攻擊,用攻擊者通過各種方式將惡意代碼注入到其他用戶的頁面中。就可以通過腳本獲取信息,發起請求之類的操作。

  • CSRF:跨站請求偽造,簡單地說,是攻擊者通過一些技術手段欺騙用戶的瀏覽器去訪問一個自己曾經認證過的網站並運行一些操作(如發郵件,發消息,甚至財產操作如轉賬和購買商品)。由於瀏覽器曾經認證過,所以被訪問的網站會認為是真正的用戶操作而去運行。CSRF 並不能夠拿到用戶的任何信息,它只是欺騙用戶瀏覽器,讓其以用戶的名義進行操作。

上面的兩種攻擊方式,如果被 XSS 攻擊了,不管是 token 還是 cookie,都能被拿到,所以對於 XSS 攻擊來說,cookie 和 token 沒有什麼區別。但是對於 CSRF 來說就有區別了。

以上面的 CSRF 攻擊為例:

  • cookie:用戶點擊了鏈接,cookie 未失效,導致發起請求後後端以為是用戶正常操作,於是進行扣款操作。

  • token:用戶點擊鏈接,由於瀏覽器不會自動帶上 token,所以即使發了請求,後端的 token 驗證不會通過,所以不會進行扣款操作。

這就是為什麼只劫持 cookie 不劫持 token 的原因。

擴展:如何防護?

XSS:所有可輸入的地方沒有對輸入數據進行處理的話,都会存在 XSS 漏洞,防禦 XSS 攻擊最簡單直接的方法,就是過濾用戶的輸入

CSRF:

  1. 驗證碼

  2. 驗證 HTTP Referer 字段

  3. 添加 token,而不是 cookie

016 常用操作 dom 方法有哪些?#

getetElementByIdgetElementsByClassNamegetElementsByTagNamegetElementsByNamequerySelectorquerySelectorAllgetAttributesetAttribute

017 瀏覽器的緩存機制#

從緩存位置上來說分為四種,並且各自有優先級,當依次查找緩存且都沒有命中的時候,才會去請求網絡。

  1. Service Worker

  2. Memory Cache

  3. Disk Cache

  4. Push Cache

Service Worker 是運行在瀏覽器背後的獨立線程。使用 Service Worker 的話,傳輸協議必須為 HTTPS。因為 Service Worker 中涉及到請求攔截,所以必須使用 HTTPS 協議來保障安全。Service Worker 的緩存與瀏覽器其他內建的緩存機制不同,它可以讓我們自由控制緩存哪些文件、如何匹配緩存、如何讀取緩存,並且緩存是持續性的。

Service Worker 實現緩存功能一般分為三個步驟:首先需要先註冊 Service Worker,然後監聽到 install 事件以後就可以緩存需要的文件,那麼在下次用戶訪問的時候就可以通過攔截請求的方式查詢是否存在緩存,存在緩存的話就可以直接讀取緩存文件,否則就去請求數據。

Memory Cache 也就是內存中的緩存,主要包含的是當前中頁面中已經抓取到的資源,例如頁面上已經下載的樣式、腳本、圖片等。讀取內存中的數據肯定比磁碟快,內存緩存雖然讀取高效,可是緩存持續性很短,會隨著進程的釋放而釋放。一旦我們關閉 Tab 頁面,內存中的緩存也就被釋放了。

Disk Cache 也就是存儲在硬碟中的緩存,讀取速度慢點,但什麼都能存儲到磁碟中,比之 Memory Cache 勝在容量和存儲時效性上。絕大部分的緩存都來自 Disk Cache。

Push Cache(推送緩存)是 HTTP/2 中的內容,當以上三種緩存都沒有命中的時候,它才會被使用。它只在會話(Session)中存在,一旦會話結束就被釋放,並且緩存時間也很短暫,在 Chrome 瀏覽器中只有 5 分鐘左右,同時它也並非嚴格執行 HTTP 頭中的緩存指令。

018 call 和 apply 的區別是什麼,哪個性能更好一些#

Function.prototype.apply 和 Function.prototype.call 的作用是一樣的,區別在於傳入參數的不同:

  • 第一个参数都是,指定函數體內 this 的指向;

  • 第二個參數開始不同,apply 是傳入帶下標的集合,數組或者類數組,apply 把它傳給函數作為參數,call 從第二個開始傳入的參數是不固定的,都是傳給函數作為參數。

call 比 apply 的性能要好,因為內部少了一次將 apply 第二個參數解構的操作。

在 es6 引入了展開操作符後,即使參數是數組,可以使用 call:

let params = [1, 2, 3, 4]
xx.call(obj, ...params)

019 為什麼通常在發送數據埋點請求的時候使用的是 1x1 像素的透明 gif 圖片?#

  1. 沒有跨域問題

  2. 不會阻塞頁面加載,影響用戶的體驗(排除 JS/CSS 文件資源方式上報)

  3. 在所有圖片中,體積最小(比較 PNG/JPG)

020 JS 多少種數據類型?#

七種:Number、String、Boolean、Object、Null、Undefined、Symbol

021 Symbol 使用場景?#

  1. 消除魔法字符

  2. Symbol 值作為屬性名避免被覆蓋

  3. 模擬類的私有方法:ES6 中的類是沒有 private 關鍵字來聲明類的私有方法和私有變量的,但是可以利用 Symbol 的唯一性來模擬。

const speak = Symbol();
class Person {
    [speak]() {
        ...
    }
}

022 HTTP 常見狀態碼#

HTTP 狀態碼共分為 5 種類型:

分類分類描述
1**信息,服務器收到請求,需要請求者繼續執行操作
2**成功,操作被成功接收並處理
3**重定向,需要進一步的操作以完成請求
4**客戶端錯誤,請求包含語法錯誤或無法完成請求
5**服務器錯誤,服務器在處理請求的過程中發生了錯誤

023 什麼是媒體查詢?響應式設計與自適應設計的區別?#

媒體查詢#

媒體查詢能在不同的條件下使用不同的樣式,使頁面在不同在終端設備下達到不同的渲染效果。

原理:允許添加表達式用以媒體查詢(包括 媒體類型 和 媒體特性),以此來選擇不同的樣式表,從而自動適應不同的屏幕分辨率。

  1. 媒體類型:將不同的設備劃分為不同的類型
  • all (所有的設備)

  • print (打印設備)

  • screen (電腦屏幕,平板電腦,智能手機)

  1. 媒體特性:用來描述設備的特點,比如寬度和高度等
  • width 網頁顯示區域完全等於設置的寬度

  • height 網頁顯示區域完全等於設置的高度

  • max-width /max-height 網頁顯示區域小於等於設置的寬度

  • min-width /min-width 網頁顯示區域大於等於設置的寬度

  • orientation: portrait (豎屏模式) | landscape (橫屏模式)

響應式設計與自適應設計#

  1. 響應式設計與自適應設計的區別?
  • 響應式設計:響應式開發一套界面,通過檢測視口分辨率,針對不同客戶端在客戶端做代碼處理,來展現不同的佈局和內容。

  • 自適應設計:自適應需要開發多套界面,通過檢測視口分辨率,來判斷當前訪問的設備是 PC 端、平板還是手機,從而返回不同的頁面。

  1. 響應式設計與自適應設計如何選取?
  • 頁面不是太複雜的情況下,采用響應式佈局的方式

  • 頁面中信息較多,佈局較為複雜的情況,采用自適應佈局的方式

  1. 響應式設計與自適應設計的優缺點?

響應式佈局

優點:靈活性強,能夠快捷解決多設備顯示適用問題

缺點:效率較低,兼容各設備工作量大;代碼較為累贅,加載時間可能會加長;在一定程度上改變了網站原有的佈局結構

自適應佈局

優點:對網站複雜程度兼容更大,代碼更高效

缺點:同一個網站需要為不同的設備開發不同的頁面,增加的開發的成本

024 介紹下 BFC 及其應用#

BFC(Formatting context)就是塊級格式上下文,是頁面盒模型佈局中的一種 CSS 渲染模式,相當於一個獨立的容器,裡面的元素和外部的元素相互不影響。

創建 BFC 的方式有:

  1. float 浮動

  2. position 為 absolute 或 fixed

  3. display 為表格佈局或者彈性佈局

  4. overflow 除了 visible 以外的值(hidden,auto,scroll)

BFC 的特性:

  1. 內部的 Box 會在垂直方向上一個接一個的放置。

  2. 垂直方向上的距離由 margin 決定

  3. BFC 的區域不會與 float 的元素區域重疊。

  4. 計算 BFC 的高度時,浮動元素也參與計算

  5. BFC 就是頁面上的一個獨立容器,容器裡面的子元素不會影響外面元素。

BFC 主要的作用是:

  1. 清除浮動

  2. 防止同一 BFC 容器中的相鄰元素間的外邊距重疊問題

025 Event Loop 概述?#

Event Loop 即事件循環,是指瀏覽器或 Node 的一種解決 JS 單線程運行時不會阻塞的一種機制,也就是我們經常使用異步的原理。

在 JS 中,任務被分為兩種,一種宏任務(MacroTask),一種叫微任務(MicroTask)。

MacroTask(宏任務):setTimeout、setInterval、setImmediate、I/O、UI Rendering。

MicroTask(微任務):Promise、MutationObserver、Process.nextTick(Node 獨有)

宏任務會被放入宏任務隊列中,微任務會被放入微任務隊列中。

JS 有一個主線程和調用棧,所有的任務都會被放到調用棧等待主線程執行。

JS 調用棧采用的是後進先出的規則,當任務執行的時候,會被添加到棧的頂部,當執行棧執行完成後,就會從棧頂移出,直到棧內被清空。

JS 代碼的具體流程:

  1. 執行全局 Script 同步代碼,這些同步代碼有一些是同步語句,有一些是異步語句;

  2. 全局 Script 代碼執行完畢後,調用棧會清空;

  3. 從微任務隊列中取出位於隊首的回調任務,放入調用棧中執行,執行完後從棧頂移出,隊列長度減 1;

  4. 繼續取出位於隊首的任務,放入調用棧中執行,以此類推,直到直到把微任務隊列中的所有任務都執行完畢。如果在執行微任務的過程中,又產生了新的微任務,那麼會加入到隊列的末尾,也會在這個周期被調用執行;

  5. 微任務隊列中的所有任務都執行完畢,此時微任務隊列為空隊列,調用棧也為空;

  6. 取出宏任務隊列中位於隊首的任務,放入中執行;

  7. 執行完畢後,調用棧為空;

  8. 重複執行微任務隊列中的任務,再執行宏任務隊列的任務;

重點:

  1. 微任務隊列中所有的任務都會被依次取出執行,直到隊列長度為空;

  2. 宏任務隊列一次只從隊列中取一個任務執行,執行完後就去執行微任務隊列中的任務;

026 如何提高首屏加載速度?#

  1. 對代碼進行壓縮,減小代碼體積

  2. 路由懶加載

  3. 第三方庫由 CDN 引入,可以減小代碼的體積,從而提高首屏加載速度

  4. SSR 服務器渲染

027 對稱加密和非對稱加密?#

對稱密碼體制使用相同的密鑰對消息進行加解密,系統的保密性主要由密鑰的安全性決定,而與算法是否保密無關。

非對稱密碼體制使用公鑰加密,使用私鑰來解密。使用非對稱密碼體制可增強通信的安全性。

常用的對稱加密算法有:DES、AES。

非對稱加密算法:RSA。

hash 算是加密嗎?不算,hash 是不可逆的,加密應該是可以根據加密後的數據還原的。

028 webpack 中 loader 和 plugin 的區別是什麼?#

loader,它是一個轉換器,將 A 文件進行編譯成 B 文件,比如:將 A.less 轉換為 A.css,單純的文件轉換過程。

plugin 是一個擴展器,它豐富了 webpack 本身,針對是 loader 結束後,webpack 打包的整個過程,它並不直接操作文件,而是基於事件機制工作,會監聽 webpack 打包過程中的某些節點,執行廣泛的任務。

029 彈性盒子中 flex: 0 1 auto 表示什麼?並求 left、right 盒子寬度?#

三個參數分別對應的是 flex-grow, flex-shrink 和 flex-basis,默認值為 0 1 auto。

  1. flex-grow 屬性定義項目的放大比例,默認為 0,即如果存在剩余空間,也不放大。

  2. flex-shrink 屬性定義了項目的縮小比例,默認為 1,即如果空間不足,該項目將縮小。

  3. flex-basis 屬性定義了在分配多餘空間之前,項目占據的主軸空間(main size)。

問題一,考察 flex-shrink:求最終 left、right 的寬度

<div class="container">
  <div class="left"></div>
  <div class="right"></div>
</div>

<style>
  * {
    padding: 0;
    margin: 0;
  }
  .container {
    width: 600px;
    height: 300px;
    display: flex;
  }
  .left {
    flex: 1 2 500px;
    background: red;
  }
  .right {
    flex: 2 1 400px;
    background: blue;
  }
</style>

對應題目:

  • 子項溢出空間的寬度為:500 + 400 - 600 = 300

  • left 收縮比例:(500 * 2) / (500 * 2 + 400 * 1) = 0.7143

  • right 收縮比例:(400 * 1) / (500 * 2 + 400 * 1) = 0.2857

對應的:

  • left 收縮寬度:0.7143 * 300 = 214.29

  • right 收縮寬度:0.2857 * 300 = 85.71

所以:

  • left 最終寬度:500 - 214.29 = 285.71

  • right 最終寬度:400 - 85.71 = 314.29

問題二,考察 flex-grow left、right 的寬度

<div class="container">
  <div class="left"></div>
  <div class="right"></div>
</div>

<style>
  * {
    padding: 0;
    margin: 0;
  }
  .container {
    width: 600px;
    height: 300px;
    display: flex;
  }
  .left {
    flex: 1 2 300px;
    background: red;
  }
  .right {
    flex: 2 1 200px;
    background: blue;
  }
</style>

剩余的空間:600 - (300 + 200) = 100

子元素的 flex-grow 的值分別為 1 和 2,剩余空間用 3 等分來分:100 / 3 = 33.3333333

所以:

  • left 最終寬度:300 + 1 * 33.33 = 333.33

  • right 最終寬度:200 + 2 * 33.33 = 266.67

擴展:flex:1 到底代表什麼? 等同於 flex:1 1 0%

030 require 和 import 的區別?#

  • import 是 es6 的一個語法標準,require 是 AMD 規範引入方式。

  • import 在代碼編譯時被加載,所以必須放在文件開頭,require 在代碼運行時被加載,所以 require 理論上可以運用在代碼的任何地方,所以 import 性能更好。

  • import 引入的對象被修改時,源對象也會被修改,相當於淺拷貝,require 引入的對象被修改時,源對象不會被修改,官網稱值拷貝,可以理解為深拷貝。

  • import 有利於 tree-shaking(移除 JavaScript 上下文中未引用的代碼),require 對 tree-shaking 不友好。

  • import 會觸發代碼分割(把代碼分離到不同的 bundle 中,然後可以按需加載或者並行加載這些文件),require 不會觸發。

目前所有的引擎都還沒有實現 import,import 最終都會被轉碼為 require,在 webpack 打包中,import 和 require 都會變為_webpack_require_。

031 H5 擁有 6 種新特性?#

  1. 語義化標籤,例如 header,footer,section,article 等語義化標籤的作用:提升頁面的閱讀性 (結構性增強),更有利於 SEO,對於使用屏幕閱讀器的人來說會更友好。

  2. 新增媒體元素,audio 和 video 標籤能夠很容易的輸出音頻或視頻流,提供便利的獲取文件信息的 API。

  3. 用於繪畫的 canvas 屬性,Canvas API 提供了一個通過 JavaScript 和 HTML 的 canvas 元素來繪製圖形的方式。它可以用於動畫、遊戲畫面、數據可視化、圖片編輯以及實時視頻處理等方面。

  4. 新增本地存儲方式:sessionStorage、localStorage。sessionStorage 用於存儲會話級別的數據,會話關閉,數據消失,不可設置過期時間。localStorage 用於存儲需要進行持久化存儲的數據,只要不主動刪除,數據不會消失。

  5. 新的技術:webworker、websocket。 webworker:用於多線程編程,websocket:客戶端與服務端雙向數據通信協議。

  6. 新增的表單控件:calendar、date、time、email、url、search。

032 CSS3 有哪些新特性?#

  • 選擇器

  • 邊框與圓角

  • 背景與漸變

  • 過渡

  • 變換

  • 動畫

033 ES6 有哪些新特性?#

  • 變量聲明:const 和 let

  • 模板字符串

  • 箭頭函數

  • 函數的參數默認值

  • 擴展運算符

  • 對象和數組解構

  • 數據類型 Symbol

  • 數據結構 Set 和 Map

  • Proxy 代理和 Reflect 反射

  • Promise 對象

  • Iterator 遍歷器

  • Generator 函數

  • Async/Await

  • Class 類繼承

  • Module 的語法 import/export

034 合併對象的有哪些方式?#

  1. 擴展運算符:const newObj = { ...obj1, ...obj2 };

  2. Object.assign()const newObj = Object.assign(obj1, obj2);

區別:擴展運算符返回一個新對象,而 Object.assign() 函數卻修改其第一個傳入對象。

035 e.target 與 e.currentTarget?#

e.target 指觸發事件的對象的引用,是事件觸發的元素。

e.currentTarget 是指向事件綁定的元素。

036 箭頭函數與普通函數(function)的區別是什麼?構造函數(function)可以使用 new 生成實例,那麼箭頭函數可以嗎?為什麼?#

箭頭函數是和普通函數相比,有以下幾點差異:

  1. 函數體內的 this 對象,就是定義時所在的對象,而不是使用時所在的對象。

  2. 不可以使用 arguments 對象,該對象在函數體內不存在。如果要用,可以用 rest 參數代替。

  3. 不可以使用 yield 命令,因此箭頭函數不能用作 Generator 函數。

  4. 不可以使用 new 命令,因為:

  5. 沒有自己的 this,無法調用 call,apply。

  6. 沒有 prototype 屬性 ,而 new 命令在執行時需要將構造函數的 prototype 賦值給新的對象的 proto

037 var、let 和 const 區別的實現原理是什麼#

var 和 let/const 的區別:

  • 塊級作用域:var 聲明的變量,不存在塊級作用域,在全局範圍內都有效,let/const 聲明的,只在它所在的代碼塊內有效

  • 不存在變量提升:var 定義變量可以先使用,後聲明,而 let/const 只可先聲明,後使用

  • 暫時性死區:let/const 聲明的變量存在暫時性死區,即只要塊級作用域中存在,那麼它所聲明的變量就綁定了這個區域,不再受外部的影響

  • 不可重複聲明: let/const 不允許在相同作用域內,重複聲明同一個變量

  • let/const 聲明的全局變量不會掛在頂層對象下面

const:

  • const 聲明之後必須馬上賦值,否則會報錯

  • const 簡單類型一旦聲明就不能再更改,複雜類型(數組、對象等)指針指向的地址不能更改,內部數據可以更改

let 一般用來聲明變量,const 聲明常量 函數

038 如何設計實現無縫輪播#

無限輪播基本插件都可以做到,不過要使用原生代碼實現無縫滾動的話我可以提點思路,因為輪播圖基本都在 ul 盒子裡面的 li 元素。

  1. 首先獲取第一個 li 元素和最後一個 li 元素,

  2. 克隆第一個 li 元素,和最後一個 li 元素,

  3. 分別插入到 lastli 的後面和 firstli 的前面,

  4. 然後監聽滾動事件,如果滑動距離超過 x 或 - x,讓其實現跳轉下一張圖或者跳轉上一張,(此處最好設置滑動距離),

  5. 然後在滑動最後一張實現最後一張和克隆第一張的無縫轉換,當到克隆的第一張的時候停下的時候,讓其切入真的第一張,則實現無線滑動,向前滑動同理

039 ES6 代碼轉成 ES5 代碼的實現思路是什麼?#

ES6 代碼轉成 ES5 代碼,可以參考 Babel 的實現方式。那么 Babel 是如何把 ES6 轉成 ES5 呢,其大致分為三步:

  1. 將代碼字符串解析成抽象語法樹,即所謂的 AST

  2. 對 AST 進行處理,在這個階段可以對 ES6 代碼進行相應轉換,即轉成 ES5 代碼

  3. 根據處理後的 AST 再生成代碼字符串

040 改變 this 的指向的方法以及區別?#

三種方式:call、apply、bind

call 與 apply 的區別:接收的參數不同,call 和 apply 第一个參數都是函數運行的作用域 this,call 方法後續傳參逐個列舉,apply 方法第二個是參數數組。

bind 與 call 和 apply 的區別:bind 的返回值是一個函數,而 call 和 apply 是立即調用。

041 如何解決移動端 Retina 屏 1px 像素問題#

  1. 伪元素 + transform scaleY (.5)

  2. border-image

  3. background-image

  4. box-shadow

042 數組裡面有 10 萬個數據,取第一個元素和第 10 萬個元素的時間相差多少#

數組可以直接根據索引取的對應的元素,所以不管取哪個位置的元素的時間複雜度都是 O (1)

得出結論:消耗時間幾乎一致,差異可以忽略不計

043 for in 和 for of 的區別#

它們兩者都可以用於遍歷,不過 for in 遍歷的是數組的索引(index),而 for of 遍歷的是數組元素值(value)。

for in 更適合遍歷對象,當然也可以遍歷數組,但是會存在一些問題,比如:

  1. index 索引為字符串型數字,不能直接進行幾何運算

  2. 遍歷順序有可能不是按照實際數組的內部順序

  3. 使用 for in 會遍歷數組所有的可枚舉屬性、包括原型,如果不想遍歷原型方法和屬性的話,可以在循環內部判斷一下,使用 hasOwnProperty () 方法可以判斷某屬性是不是該對象的實例屬性

for (let index in arr) {
  if (arr.hasOwnProperty(index)) {
  }
}

for of 遍歷的是數組元素值,而且 for of 遍歷的只是數組內的元素,不包括原型屬性和索引。

總結:

  1. for in 遍歷的是數組的索引(即鍵名),而 for of 遍歷的是數組元素值

  2. for in 總是得到對象的 key 或數組、字符串的下標

  3. for of 總是得到對象的 value 或數組、字符串的值

044 什麼是閉包?使用閉包應該注意什麼?#

對於 JavaScrip 而言,函數內部可以直接讀取全局變量,在函數外部無法讀取函數內的局部變量。而閉包便是指 有權訪問另外一個函數作用域中的變量的函數,也就是能夠讀取其他函數內部變量的函數。

使用閉包應該注意什麼?

  1. 代碼難以維護:閉包內部是可以訪問上級作用域,改變上級作用域的私有變量,使用一定要小心,不要隨便改變上級作用域私有變量的值。

  2. 內存泄漏:由於閉包會使得函數中的變量都保存在內存中,內存消耗很大,所以不能濫用閉包,不再用到的內存,沒有及時釋放,易造成內存泄漏。

  3. this 指向:閉包的 this 指向的是 window。

閉包應用場景:回調、IIFE、函數防抖、節流、柯里化、模塊化。

從最終渲染的 DOM 來看,這兩者都是鏈接,都是 <a> 標籤,區別是:

  • <Link> 是 react-router 裡實現路由跳轉的鏈接,一般配合 <Route> 使用,react-router 接管了其默認的鏈接跳轉行為,區別於傳統的頁面跳轉,<Link> 的 “跳轉” 行為只會觸發相匹配的 <Route> 對應的頁面內容更新,而不會刷新整個頁面。

  • <a> 標籤就是普通的超鏈接了,用於從當前頁面跳轉到 href 指向的另一個頁面(非錨點情況)。

046 如何實現懶加載?#

路由懶加載:對於 React/Vue 這類 SPA(單頁應用程序)來說,當打包構建應用時,JS 包會變得非常大,影響頁面加載。路由懶加載能把不同路由對應的組件分割成不同的代碼塊,然後當路由被訪問的時候才加載對應組件,這樣就會更加高效。

Vue 路由懶加載的實現:

對於 Vue 來說,Vue Router 支持動態導入,可以用動態導入代替靜態導入。

// import UserDetails from './views/UserDetails'
// 替換成
const UserDetails = () => import('./views/UserDetails')

把組件按組分塊,有時候想把某個路由下的所有組件都打包在同個異步塊(chunk)中。只需要使用命名 chunk,一個特殊的註釋語法來提供 chunk name (需要 Webpack> 2.4),webpack 會將任何一個異步模塊與相同的塊名稱組合到相同的異步塊中。

const UserDetails = () => import(/* webpackChunkName: "group-user" */ './UserDetails.vue')
const UserDashboard = () => import(/* webpackChunkName: "group-user" */ './UserDashboard.vue')
const UserProfileEdit = () => import(/* webpackChunkName: "group-user" */ './UserProfileEdit.vue')

React 實現路由懶加載:

  • 通過 React.lazy() + Suspense 實現組件的動態加載

  • import() 拆包

047 什麼是 MVVM?#

MVVM 是 Model-View-ViewModel 縮寫,也就是把 MVC 中的 Controller 演變成 ViewModel。Model 層代表數據模型,View 代表 UI 組件,ViewModel 是 View 和 Model 層的橋樑,數據會綁定到 viewModel 層並自動將數據渲染到頁面中,視圖變化的時候會通知 viewModel 層更新數據。

048 Vue 的父組件和子組件生命週期鉤子執行順序是什麼#

  1. 加載渲染過程:父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted

  2. 子組件更新過程:父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated

  3. 父組件更新過程:父 beforeUpdate -> 父 updated

  4. 銷毀過程:父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

049 為什麼 Vuex 的 mutation 和 Redux 的 reducer 中不能做異步操作#

因為更改 state 的函數必須是純函數,純函數既是統一輸入就會統一輸出,沒有任何副作用;如果是異步則會引入額外的副作用,導致更改後的 state 不可預測。

因為異步操作是成功還是失敗不可預測,什麼時候進行異步操作也不可預測;當異步操作成功或失敗時,如果不 commit (mutation) 或者 dispatch (action),Vuex 和 Redux 就不能捕獲到異步的結果從而進行相應的操作。

擴展:redux 為什麼要把 reducer 設計成純函數?

答:純函數既是統一輸入就會統一輸出,沒有任何副作用。redux 的設計思想就是不產生副作用,數據更改的狀態可回溯,所以 redux 中處處都是純函數。

050 在 Vue 中,子組件為何不可以修改父組件傳遞的 Prop?#

為了保證數據的單向流動,便於對數據進行追蹤,避免數據混亂。

051 雙向綁定和 vuex 是否衝突#

在嚴格模式中使用 Vuex,當用戶輸入時,v-model 會試圖直接修改屬性值,但這個修改不是在 mutation 中修改的,所以會拋出一個錯誤。當需要在組件中使用 vuex 中的 state 時,有 2 種解決方案:

  1. 在 input 中綁定 value (vuex 中的 state),然後監聽 input 的 change 或者 input 事件,在事件回調中調用 mutation 修改 state 的值

  2. 使用帶有 setter 的雙向綁定計算屬性。

<input v-model="message" />

computed: {
    message: {
        get () {
            return this.$store.state.obj.message
        },
        set (value) {
            this.$store.dispatch('updateMessage', value);
        }
    }
}
mutations: {
    UPDATE_MESSAGE (state, v) {
        state.obj.message = v;
    }
}
actions: {
    update_message ({ commit }, v) {
        commit('UPDATE_MESSAGE', v);
    }
}

052 Vue 的響應式原理中 Object.defineProperty 有什麼缺陷?為什麼在 Vue3.0 採用了 Proxy,拋棄了 Object.defineProperty?#

  1. Object.defineProperty 無法監控到數組下標的變化,導致通過數組下標添加元素,不能實時響應;

  2. Object.defineProperty 只能劫持對象的屬性,從而需要對每個對象每個屬性進行遍歷,如果屬性值是對象,還需要深度遍歷。Proxy 可以劫持整個對象,並返回一個新的對象。

  3. Proxy 不僅可以代理對象,還可以代理數組,還可以代理動態增加的屬性。

053 Vue 中的 computed 是如何實現的?#

computed 本身是通過代理的方式代理到組件實例上的,所以讀取計算屬性的時候,執行的是一個內部的 getter,而不是用戶定義的方法。

computed 內部實現了一個惰性的 watcher,在實例化的時候不會去求值,其內部通過 dirty 屬性標記計算屬性是否需要重新求值。當 computed 依賴的任一狀態發生變化,都会通知這個惰性 watcher,讓它把 dirty 屬性設置為 true。所以,當再次讀取這個計算屬性的時候,就會重新去求值。

惰性 watcher / 計算屬性在創建時是不會去求值的,是在使用的時候去求值的。

054 Vue 中的 computed 和 watch 的區別在哪裡#

computed:計算屬性,是由 data 中的已知值,得到的一個新值。這個新值只會根據已知值的變化而變化,其他不相關的數據的變化不會影響該新值。計算屬性不在 data 中。別人變化影響我自己。

watch:監聽器,監聽數據的變化,監聽的數據就是 data 中的已知值,我的變化影響別人。

  1. watch 擅長處理的場景:一個數據影響多個數據

  2. computed 擅長處理的場景:一個數據受多個數據影響

055 v-if、v-show、v-html 的原理是什麼,它是如何封裝的?#

  • v-if 會調用 addIfCondition 方法,生成 vnode 的時候會忽略對應節點,render 的時候就不會渲染;

  • v-show 會生成 vnode,render 的時候也會渲染成真實節點,只是在 render 過程中會在節點的屬性中修改 show 屬性值,也就是 display;

  • v-html 會先移除節點下的所有節點,調用 html 方法,通過 addProp 添加 innerHTML 屬性,歸根結底還是設置 innerHTML 為 v-html 的值;

056 v-show 和 v-if 指令的共同點和不同點?#

共同點:都能控制元素的顯示和隱藏

不同點:實現本質方法不同,v-show 本質就是通過控制 css 中的 display 設置為 none,控制隱藏,只會編譯一次;v-if 是動態的向 DOM 樹內添加或者刪除 DOM 元素,若初始值為 false ,就不會編譯了,而且 v-if 不停的銷毀和創建比較消耗性能。

總結:如果要頻繁切換某節點,使用 v-show (切換開銷比較小,初始開銷較大)。如果不需要頻繁切換某節點使用 v-if(初始渲染開銷較小,切換開銷比較大)。

057 Vue 組件之間有哪幾種通信方式?#

  • 父子通信:父向子傳遞數據是通過 props,子向父是通過 $emit;通過 $parent(父組件實例)/ $children(子組件實例)通信;$ref 也可以訪問組件實例;provide/inject;$attrs/$listeners;

  • 兄弟通信:Event Bus、Vuex

  • 跨級通信:Event Bus、Vuex;provide/inject; $attrs/$listeners;

058 如何實現瀏覽器多標籤頁之間通信?#

  1. localStorage 實現通信
window.addEventListener('storage', (e) => {
  console.info('localStorage發生變化:', e)
})
  1. websocket

原理比較簡單,假如 pageA 和 pageB 都與服務器建立了 websocket 連接,那麼兩個頁面都可以實時接收服務端發來的消息,也可以實時向服務端發送消息。如果 pageA 更改了數據,那麼向服務端發送一條消息,服務端再將這條消息發送給 pageB 即可,這樣就簡單實現了兩個標籤頁之間的通信。

  1. SharedWorker

sharedWorker 就是 webWorker 中的一種,它可以由所有同源頁面共享,利用這個特性,就可以使用它來進行多標籤頁之前的通信。

它和 webSocket 實現多頁面通訊的原理很類似,都是發送數據和接收數據這樣的步驟,shardWorker 就好比的 webSocket 服務器。

// worker.js
const set = new Set()
onconnect = (event) => {
  const port = event.ports[0]
  set.add(port)

  // 接收信息
  port.onmessage = (e) => {
    // 廣播信息
    set.forEach((p) => {
      p.postMessage(e.data)
    })
  }

  // 發送信息
  port.postMessage('worker廣播信息')
}

// pageA
const worker = new SharedWorker('./worker.js')
worker.port.onmessage = (e) => {
  console.info('pageA收到消息', e.data)
}

// pageB
const worker = new SharedWorker('./worker.js')
worker.port.postMessage(`客戶端B發送的消息:HELLO`)
  1. cookie + setInterval

原理是在需要接收消息的頁面不斷輪詢去查詢 cookie,然後發送消息的頁面將數據存儲在 cookie 中,這樣就實現了簡單的數據共享。

setInterval(() => {
  //加入定時器,讓函數每一秒就調用一次,實現頁面刷新
  console.log('cookie', document.cookie)
}, 1000)

059 事件代理的原理以及優缺點是什麼?#

什麼是事件代理:由於事件會在冒泡階段向上傳播到父節點,因此可以把子節點的監聽函數統一處理。指定一個事件處理程序,就可以管理某一類型的所有事件,這就叫事件代理。

原理:利用事件冒泡機制
優點:

  1. 可以大量節省內存佔用,減少事件註冊

  2. 可以實現當新增子對象時,無需再對其進行事件綁定,對於動態內容部分尤為適合

缺點:

  1. 事件代理的實現依靠的冒泡,因此不支持事件冒泡的事件就不適合使用事件代理

  2. 不是所有的事件綁定都適合使用事件代理,不恰當使用反而可能導致不需要綁定事件的元素也被綁上了事件

060 Vue 在 v-for 時給每項元素綁定事件需要用事件代理嗎?為什麼?#

首先我們需要知道事件代理主要有什麼作用?

  • 事件代理能夠避免我們逐個的去給元素新增和刪除事件

  • 事件代理比每一個元素都綁定一個事件性能要更好

從 vue 的角度上來看上面兩點

  • 在 v-for 中,我們直接用一個 for 循環就能在模板中將每個元素都綁定上事件,並且當組件銷毀時,vue 也會自動給我們將所有的事件處理器都移除掉。所以事件代理能做到的第一點 vue 已經給我們做到了

  • 在 v-for 中,給元素綁定的都是相同的事件,所以除非上千行的元素需要加上事件,其實和使用事件代理的性能差別不大,所以也沒必要用事件代理

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。