2019年我总结的前端面试题
title: 2019年我總結的面試題 date: 2019-05-11 14:43:09 tags:
說一下Promise
Promise是什么?
- Promise是一種用于解決異步問題的思路、方案或者對象方式。
Promise怎么用?
- Promise是一個對象,所以先用new的方式創建一個,然后給它傳一個函數作為參數,這個函數有兩個參數,一個叫reolve,另一個叫reject、 緊接著,就用then來進行調用
Promise原理
-
在Promise內部,有一個狀態管理器的存在,有三種狀態: pending、fulfilled、rejected
(1) promise初始化狀態為pending
(2) 當前調用resolve(成功), 會由pending => fulfilled
(3) 當調用reject(失敗), 會由pending => rejected
跨域
什么是跨域
協議、端口和域名不一致導致的跨域 跨域是因為瀏覽器需要遵守同源策略,發出的請求即使相應成功,也被瀏覽器攔截下來
同源策略
同源策略限制了從同一個源加載的文檔或腳本如何與來自另一個源的資源進行交互、這是一個用于隔離潛在惡意文件的重要安全機制、
為什么
如果缺少了同源策略,瀏覽器很容易受到XSS、CSFR等攻擊。
1、 防御 XSS 攻擊
- XSS,即 Cross Site Script,中譯是跨站腳本攻擊。
- HttpOnly 防止劫取 Cookie
- 用戶的輸入檢查
- 服務端的輸出檢查
2、防御 CSRF 攻擊
- CSRF,即 Cross Site Request Forgery,中譯是跨站請求偽造,是一種劫持受信任用戶向服務器發送非預期請求的攻擊方式。
- 驗證碼
- Referer Check
- Token驗證
跨域的解決方案
1、通過jsonp跨域 2、document.domain + iframe跨域 3、location.hash + iframe 4、window.name + iframe跨域 5、postMessage跨域 6、跨域資源共享(CORS) 7、nginx代理跨域 8、nodejs中間代理跨域 9、WebSocket協議跨域
jsonp原理
jsonp的核心則是動態添加 script 標簽調用服務器提供的js腳本,允許用戶傳遞一個callback參數給服務器,然后服務器返回數據時會將這個callback參數作為函數名老包裹JSON數據,這樣客戶端就可以隨意定制自己的函數來自動處理返回數據了
- 僅支持GET方法
如何進行網站性能優化
1、Content方面
- 減少HTTP請求:合并文件、CSS精靈、inline image
- 減少DNS查詢: DNS查詢完之前瀏覽器不能從這個主機下載任何文件、方法:DNS緩存、講資源分布到恰當的數量的主機名,平衡并行下載和DNS查詢
- 避免重定向 : 多余的中間訪問
- 使用AJAX緩存
- 非必須組件延遲加載
- 未來所需組件預加載
- 減少DOM元素數量
- 將資源放到不同的域下面:瀏覽器同時從一個域下載資源的數目有限,增加域可以提高并行下載量
- 減少iframe數量
- 不要404
2、Server方面
- 使用CDN
- 添加Expires或者Cache-Control: 當Cache-Control和Expires同時存在時,Cache-Control會覆蓋Expires。相關鏈接
- 使用Gzip壓縮
- 配置Etag
- Flush Buffer Early
- Ajax使用GET進行請求
- 避免空src的img標簽
3、Cookie方面
- 減小Cookie
- 引入資源的域名不要包含cookie
4、CSS方面
- 將樣式表放到頂部
- 不要使用CSS表達式
- 不使用@import
- 不使用IE的Filter
5、JavaScript
- 將腳本放到頁面的底部
- 將JavaScript和CSS從外部引入
- 壓縮JavaScript和CSS
- 刪除不需要的腳本
- 減少DOM的查詢
- 合理設計事件監聽器
6、圖片方面
7、移動方面
- 保證組件小于25K
- Pack Components into a Multipart Document
從瀏覽器地址欄輸入url到顯示頁面的步驟(以HTTP為例)
大概流程
- URL輸入
- DNS解析
- TCP連接
- 發送HTTP請求
- 服務器處理請求
- 服務器響應請求
- 瀏覽器解析渲染頁面
- 連接結束
1、在瀏覽器數地址欄輸入URL
2、瀏覽器查看緩存,如果請求資源在緩存中并且新鮮,跳轉到轉碼步驟
- 如果資源為緩存,發起新請求
- 如果已緩存,檢驗是否足夠新鮮,足夠新鮮直接提供給客戶端,否則與服務器進行驗證。
- 檢驗新鮮通常有兩個HTTP頭進行控制 Expires 和 Cache-Control
- HTTP1.0提供Expires,值為一個絕對值表示
- HTTP1.1增加了Cache-Control : max-age=,值為以秒為單位的最大新鮮時間
3、瀏覽器解析URL獲取協議,主機,端口,path
4、瀏覽器組裝一個HTTP(GET)請求報文
5、瀏覽器獲取主機ip地址,過程如下:
- 瀏覽器緩存
- 本機緩存
- hosts文件
- 路由器緩存
- ISP DNS緩存
- DNS遞歸查詢(可能存在負載均衡導致每次IP不一樣)
6、打開一個sokcet與目標地址,端口建立TCP鏈接, 三次握手如下:
- 客戶端發送一個TCP的SYN=1,Seq=X的包到服務器端口
- 服務器發送SYN=1,ACK=X+1,Seq=Y的響應包
- 客戶端發送ACK=Y+1,Seq=Z
7、TCP鏈接建立后發送HTTP請求
8、服務器接受請求并解析,將請求轉發到服務程序,如虛擬機使用HTTP Host頭部判斷請求的服務程序
9、服務器檢查HTTP請求頭是否包含緩存驗證信息如果驗證緩存新鮮,返回304等對應狀態碼
10、處理程序讀取完整請求并準備HTTP響應,可能需要查詢數據庫等操作
11、服務器將響應報文通過TCP鏈接發送回瀏覽器
12、瀏覽器接受HTTP響應,然后根據情況選擇關閉TCP連接或者保留重用,關閉TCP連接的四次握手如下:
- 主動發送Fin=1,Ack=Z,Seq=X報文
- 被動發送ACK=X+1,Seq=Z報文
- 被動發送Fin=1,ACK=X,Seq=Y報文
- 主動發送ACK=Y,Seq=X報文
13、瀏覽器檢查響應狀態碼:是否為1XX、3XX、4XX、5XX,這些情況處理與2XX不同
14、如果資源可緩存,進行緩存
15、對響應進行解碼(例如gzip壓縮)
16、根據資源類型決定如何處理(假設資源為HTML文檔)
17、解析HTML文檔、構件DOM樹,下載資源,構造CSSOM樹,執行js腳本,這些操作沒有嚴格的先后順序,以下分別解釋
18、構建DOM樹:
- Tokenizing: 根據HTML規范將字符流解析為標記
- Lexing:詞法分析將標記轉換為對象并定義屬性和規則
- DOM construction: 根據HTML標記關系將對象組成DOM樹
19、解析過程中遇到圖片、樣式表、js文件,啟動下載
20、構建CSSOM樹
- Tokenizing: 字符流轉換為標記流
- Node:根據標記創建節點
- CSSOM:節點創建CSSOM樹
21、根據DOM樹和CSSOM樹構建渲染樹:
- 從DOM樹的根節點遍歷所有可見節點,不可見節點包括:1)script,meta這樣本身不可見的標簽。2)被CSS隱藏的節點,入display:none
- 對每一個可節點,找到恰當的CSSOM規則并應用
- 發布可視節點的內容和計算樣式
22、js解析如下
- 瀏覽器創建Document對象并解析HTML,將解析到的元素和文本節點添加到文檔中,此時document.readystate為loading
- HTML解析器遇到沒有async和defer的script時,將他們添加到文檔中,然后執行行內或者外部腳本。這些腳本同步執行,并且在腳本下載和執行時解析器會暫停。這樣就可以用document.write()把文本插入到輸入流中。同步腳本經常定義為函數和注冊事件處理事件,他們可以遍歷和操作script和他們之前的文檔內容。
- 當解析器遇到設置了async屬性的script時,開始下載腳本并繼續解析文檔。腳本在它下載完成后盡快執行,但是解析器不會停下來等它下載。異步腳本禁止使用document.write(),它們可以訪問自己script和之前的文檔元素
- 所有deter腳本會按照在文檔上出現的順序執行,延遲腳本能訪問完整文檔時,禁止使用document.write()
- 瀏覽器在Document對象上觸發DOMContentLoaded事件
- 此時文檔完成解析完成,瀏覽器可能還在等待如圖片等內容加載,等這些內容完成載入并且所有異步腳本完成和執行,document.readState變為complete,window觸發load事件
23、顯示頁面(HTML解析過程中會逐步顯示頁面)
移動端開發自適應頁面如何做?
1、通過meta標簽設置viewport,移動端的理想適口。
- <meta name="viewport" content="width=width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
2、設置rem單位來進行適配、加上Flex布局、百分比布局
3、其它方案,響應式適配、vw+rem
rem原理
- rem是是指相對于根元素的字體大小的單位
- 比如設置html font-size=100px;那么1rem=100px;之后的所有元素都可以用這個基準值來設置大小;
- rem作用于非根元素時,相對于根元素字體大小;rem作用于根元素字體大小時,相對于其出初始字體大小——MDN
說一下this
JavaScript 函數中的 this 指向并不是在函數定義的時候確定的,而是在調用的時候確定的。換句話說,函數的調用方式決定了 this 指向。 函數調用的方式
- 直接調用 直接調用,就是通過 函數名(...) 這種方式調用
- 方法調用 方法調用是指通過對象來調用其方法函數,它是 **對象.方法函數(...)** 這樣的調用形式
- new關鍵字調用
- 通過 bind() 將函數綁定到對象之后再進行調用
- 通過 call()、apply() 進行調用
箭頭函數的特點?
官方解釋:箭頭函數表達式的語法比函數表達式更簡潔,并且沒有自己的this,arguments,super或 new.target。
-
引用箭頭函數有兩個方面的作用:更簡短函數和并且不綁定this
-
箭頭函數不會創建this,它只會從自己的作用域鏈上一層繼承this。
-
簡而言之,箭頭函數,永遠指向當前調用的對象
== 和 === 的區別?
- == 會進行隱式轉換,比較前將兩個被比較的值轉換為相同類型。然后比較兩個值是否相等
- === 不進行隱式轉換,會比較類型和值
CSS選擇器優先級
- 每個選擇器都有權值,權值越大越優先
- 繼承的樣式優先級低于自身制定樣式
- !important優先級最高,js也無法修改
- 權值相同的時候,靠近元素的樣式優先級搞,順序為內聯樣式 > 內部樣式表 > 外部樣式表
BFC
什么是BFC
BFC就是"塊級格式化上下文"的意思,創建了BFC的元素就是一個獨立的盒子,不過只有Block-level Box 可以參與創建BFC,它規定了內部的Block-level Box如何布局,并且與這個獨立盒子里的布局不受外部影響,當然它不會影響到外面的元素。
BFC特性:
- 內部的Box會在垂直方向,從頂部開始一個接一個地放置
- Box垂直方向的距離由margin決定。屬于同一個BFC的兩個相鄰Box的margin會發生疊加
- 每個元素的margin box的左邊,與包含的 border box 的左邊相接觸(對于從左往右的格式化,否則相反)。即使存在浮動也是如此。
- BFC的區域不會與float box疊加
- BFC就是頁面的一個隔離的獨立容器,容器里面的子元素不會受影響到外面的元素,反之亦然。
- 計算BFC的高度時,浮動元素也參與計算
如何觸發BFC
- 根元素或包含根元素的元素
- 浮動元素,float 除了 none 以外
- 絕對定位元素, position 為 absolute、fixed
- display 為 inline-block、table-cell、table-caption、flow-root
- overflow 值不為 visible 的元素
- 彈性元素(display 為 flex 或 inline-flex 元素的直接子元素)
- 網格元素 (display 為 grid 或 inline-grid 元素的直接子元素)
- 多列容器(元素的 column-count 或 column-width 不為 auto,包括 column-count 為 1)
- column-span 為 all 的元素始終會創建一個新的BFC,即使該元素沒有包裹在一個多列容器中(標準變更,Chrome bug)
EventBus如何實現?
利用發布/訂閱模式,發布/訂閱模式由一個發布者、多一個訂閱者以及一個調度中心所組成。訂閱者們先在調度中心訂閱某一事件并注冊相應的回調函數,當某一個時刻發布者發布了一個事件,調度中心取出訂閱了該事件的訂閱者們所注冊的回調函數來執行。
在發布/訂閱模式中,訂閱者和發布者并不需要關心對方的狀態,訂閱者只管訂閱事件并注冊回調、發布者只管發布事件,其余一切交給調度中心來調度,從而實現解耦。
Vue雙向綁定的原理
Vue是采用數據劫持結合發布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter、getter,在數據變動時發布消息給訂閱者,觸發響應的監聽回調。
具體步驟:
第一步:需要 Observe 的數據對象進行遞歸遍歷,包括子屬性對象的屬性,都加上 setter 和 getter。這樣的話,給這個對象的某個值賦值,就會觸發setter,那么就能監聽到數據變化。
第二步:Compile 解析模板指令,將模板中的變量替換成數據,然后初始化渲染頁面視圖,并將每個指令對應的節點綁定更新函數,添加監聽數據的訂閱者,一旦數據有變動,收到通知,更新視圖
第三步:Watcher 訂閱者是 Observe 和 Compile 之間通信的橋梁,主要的事情是:
1、在自身實例化時往屬性訂閱器(dep)里面添加自己
2、自身必須有一個update()
3、待屬性變動dep.notify()通知時,能調用自身的 update() 方法,并觸發 Compile 中綁定回調,則功成身退。
第四步:MVVM作為數據綁定的入口,整合 Observe、Compile 和 Watcher 三者,通過 Observe 來監聽自己的 Model 數據變化。 通過 Compile 來解析編譯模板指令,最終利用 Watcher 搭起 Observe 和 Compile 之間的通信橋梁; 達到數據變化 -> 視圖更新; 視圖交互(input) -> 數據 Model 變更的雙向綁定效果。
Vue Computed的實現原理和緩存原理
第一步:創建一個computedWathcers 空對象, 對 computed 對象遍歷,獲取計算屬性每一個 userDef(自定義的函數或對象),然后嘗試獲取 userDef 的getter,并且為每一個 getter 添加一個watcher
第二步:判斷遍歷 computed 對象的key,是否已經存在 data 和 props 所占用,存在則發出警告,不存在就調用 defineComputed 函數,給對應的key添加getter 和 setter
第三步:在調用 defineComputed 函數,會進行依賴收集 computedWatcher ,通過computedWatcher來進行派發通知,更新視圖
第四步:緩存就是在獲取 getter 數據的,判斷是否值相等,相等的話就直接返回,不再進行更新視圖
MVVM框架是什么?它和其它框架(Jquery)的區別是什么?哪些場景適合?
MVVM分為Model、View、ViewModel三者
- Model 代表數據模型,數據和業務邏輯都在Model層中定義
- View 代表UI視圖,負責數據展示
- ViewModel 負責監聽 Model 中數據的改變并且控制視圖更新,處理用戶交互操作:
Model 和 View 并無直接關聯,而是通過 ViewModel 來進行聯系的, Model 和 ViewModel 之間有著雙向數據綁定的聯系。因此當 Model 中的數據改變時會觸發 View 層的刷新,View 中由于用戶交互操作而改變的數據也會在 Model 中同步
區別:這種模式實現了 Model 和 View的數據自動同步,因此開發時這需要要專注對數據的維護操作即可,而不需要自己操作dom 場景:數據操作比較多的場景,更加便捷
nextTick 實現原理
JS,是單線程的,利用JS的事件循環
事件循環大致分為以下幾個步驟:
(1) 所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)
(2) 主線程之外,還存在一個"任務隊列"(task queue)。只要異步任務有了運行結果,就在"任務隊列"之中放置一個事件。
(3) 一旦"執行棧"中的所有同步任務執行完畢,系統就會讀取"任務隊列",看看里面有哪些事件。哪些對應的異步任務,于是結束等待狀態,進入執行棧,開始執行。
(4) 主線程不斷重復上面的第三步
宏任務(macro task) 和 微任務(micro task)
先執行宏觀任務,再執行微觀
- 宏觀任務:setTimeout、MessageChannel、postMessage、setImmediate...
- 微觀:MutationObsever、Promise.then
nextTick原理:
-
會有一個callbacks數組,接受nextTick的回調函數,push進去
-
首先判斷是否支持Promise,支持則利用的Promise.then進行調用遍歷調用callbacks數組
-
判斷是否支持 MutationObserver,支持則利用 MutationObserver 遍歷調用callbacks數組
-
判斷是否支持 setImmediate,支持則利用 setImmediate 遍歷調用callbacks數組
-
都不支持,則利用setTimeout進行遍歷調用 callbacks數組
面試回答 : 它的邏輯也很簡單,把傳入的回調函數 cb 壓入 callbacks 數組,最后一次性地根據 useMacroTask 條件執行 macroTimerFunc 或者是 microTimerFunc,而它們都會在下一個 tick 執行 flushCallbacks,flushCallbacks 的邏輯非常簡單,對 callbacks 遍歷,然后執行相應的回調函數。
什么是虛擬 dom ?
VNode是對真實 DOM 的一種抽象描述,它的核心定義無非就幾個關鍵屬性,標簽名、數據、子節點、鍵值等,其它屬性都是用來擴展VNode的靈活性以及實現一些特殊 feature的。
Virtual DOM 除了它的數據結構的定義,映射到真實的 DOM 實際上要經歷 VNode的 create、diff、 patch等過程。
Vue組件之間的通信
-
父子組件通信,props、emit、ref調用函數
-
兄弟組件通信,vuex、eventBus
說一下Vuex
vuex有哪幾種屬性?
vuex具有五種屬性: state、getter、mutation、action、module
vuex的state特性是?
-
vuex就是一個倉庫,倉庫里面放很多對象。state就是數據存放地,對應于一般vue對象里面的data
-
state里面存放的數據是響應式的
vuex的getter特性是?
-
getters可以對state進行計算操作
-
可以在多組件之間復用
vuex的mutation特性是?
-
action類似于mutation
-
action提價的是mutation,而是不是直接變更狀態
-
action可以包含任何異步操作
不用vuex會帶來什么問題?
-
可維護性會下降,你要想修改數據,你得維護三個地方
-
可讀性下降,因為一個組件里的數據,你根本看不出來是從哪來的
-
增加耦合,大量的上傳派發,會讓耦合性大大的增加,本來Vue用Component就是為了減少耦合,現在這么用,和組件化的初衷相背。
請詳細說下你對vue生命周期的理解?
總共分為8個階段創建前/后,載入前/后,更新前/后,銷毀前/后
創建前/后: 在beforeCreated階段,vue實例的掛載元素el還沒有。
載入前/后: 在beforeMount階段,vue實例的$el和data都初始化了,但還是掛載之前為虛擬的dom節點,data.message還未替換。在mounted階段,vue實例掛載完成,data.message成功渲染。
更新前/后: 當data變化時,會觸發beforeUpdate和updated方法。
銷毀前/后: 在執行destroy方法后,對data的改變不會觸發周期函數,說明此時vue實例已經解除了事件監聽以及和dom的綁定,但是dom結構依然存在
請說下封裝 vue 組件的過程?
首先,組件可以提升整個項目的開發效率。能夠把頁面抽象成多個相對獨立的???#xff0c;解決了我們傳統項目開發:效率低、難維護、復用性等問題。
然后,使用Vue.extend方法創建一個組件,然后使用Vue.component方法注冊組件。子組件需要數據,可以在props中接受定義。而子組件修改好數據后,想把數據遞給父組件。可以采用emit方法。
Proxy 和 Object.defineProperty 的優劣?
-
Proxy有多達13種攔截方法,不限于apply、ownKeys、deleteProperty、has等等是 **Object.defineProperty()**不具備的
-
Proxy返回的是一個新對象,我們可以只操作新的對象達到目的,而 Object.defineProperty 只能遍歷對象屬性直接修改
-
Proxy作為新標準將受到瀏覽器廠商重點持續的性能優化,也就是傳說中的新標準的性能紅利
-
當然,Proxy的劣勢就是兼容性問題,而且無法用polyfill磨平,因此Vue的作者才聲明需要等到下個大版本(3.0)才能用Proxy重寫。
聊一聊常見的瀏覽器端數據存儲方案
數據存儲方案:
- Cookie
- Web存儲(localStorage和sessionStorage)
- IndexedDB
大概說一下Cookie和localStorage、sessionStorage的功能特性。問到的話,Cookie的缺點就是,存儲量少、數據大影響性能、只能儲存字符串、安全性問題、需要檢查Cookie能否使用
Flexible布局方案的原理
- 獲取document的適口寬度 除以 10
- 得出 1rem = viewWidth / 10
- 然后設置 html的font-size為 rem + 'px'
為什么會有深拷貝和淺拷貝?日常開發中如何使用?,如何實現一個深拷貝?
/*** @desc 深拷貝,支持常見類型* @param {Any} values* @return {Any}*/ function deepClone(values) {var copy;// Handle the 3 simple types, and null or undefinedif (null == values || "object" != typeof values) return values;// Handle Dateif (values instanceof Date) {copy = new Date();copy.setTime(values.getTime());return copy;}// Handle Arrayif (values instanceof Array) {copy = [];for (var i = 0, len = values.length; i < len; i++) {copy[i] = deepClone(values[i]);}return copy;}// Handle Objectif (values instanceof Object) {copy = {};for (var attr in values) {if (values.hasOwnProperty(attr)) copy[attr] = deepClone(values[attr]);}return copy;}throw new Error("Unable to copy values! Its type isn't supported."); } 復制代碼如果是一個數組,就聲明一個數據組,然后循環遍歷,遞歸賦值。 如果是一個對象,就聲明一個對象,然后判斷是否子元素,遞歸賦值
除了遞歸,我們還可以借用JSON對象的parse和stringify
function deepClone(obj){let _obj = JSON.stringify(obj),objClone = JSON.parse(_obj);return objClone } 復制代碼轉載于:https://juejin.im/post/5cfdfea7f265da1bc64bb710
總結
以上是生活随笔為你收集整理的2019年我总结的前端面试题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 零基础前端入门,真正难在哪里?简说编程思
- 下一篇: 2.3微秒的特征点匹配