高级Web前端必会面试题知识点,大厂面试必备
高級Web前端工程師必會面試題,這里只是整理一些范圍知識點,并沒有特別具體的面試題目,只要把這些知識點搞明白了,面試題都不是問題。
最近整理了一套 2021最新最全的前端面試題集錦(持續更新中),內容涉及前端各個方面,并且每道題目基本都有對應得答案解析,是你面試必備寶典。
騷年,加油!高薪等你。
目錄
- 一、HTML+CSS 系列
- 1、布局的三種模式
- 2、移動端布局適配設備的方案
- 3、`1px`邊框的解決方案
- 4、BFC(Block Formatting Contexts)
- 5、BootStrap布局原理
- 6、Sass/Less/Stylus區別,聯系,在什么場景下使用
- 二、JavaScript 系列
- 1、變量對象 + 作用域鏈 + this(執行上下文)
- 2、閉包
- 3、原型與原型鏈
- 4、繼承
- 5、函數節流和防抖
- 6、函數柯里化與反柯里化
- 7、設計模式
- 8、ES6新增API(`Proxy`, `Reflect`, `Promise`, `Generator`, `async`, `Decorator`, `Class`)
- 9、簡述ES6的新特性
- 10、瀏覽器渲染頁面的過程
- 11、瀏覽器緩存(強緩存,協商緩存)
- 12、瀏覽器端 Event loop
- 13、路由實現原理
- 14、session、cookie 與 token 認證
- 15、跨域的解決方案
- 16、Ajax (axios、fetch、原生xhr)
- 三、webpack 系列
- 1、webpack 細節
- 2、webpack 和 gulp 的區別
- 3、webpack 從啟動構建到輸出結果經歷了一系列過程
- 4、gulp基本使用
- 四、nodejs 系列
- 1、Node.js的 Event loop
- 2、Node中間件
- 3、Node三大框架:Express、Koa、Nest對比
- 4、Node.js 模塊(CommonJS、模塊分類、模塊導出、模塊加載)
- CommonJS
- 模塊分類
- 模塊導出
- 模塊加載過程
- 五、其他
- 1、http和應用編程相關的問題
- 2、git及git工作流
- 3、數據庫(MySQL、MongoDB)
- 4、前后端模板(artTemplate、ejs)
- 六、Vue 高級
- 1、Vue.js 動態組件與異步組件
- 2、處理邊界情況(`$root`,`$parent`,`$refs`,依賴注入,循環引用,`$forceUpdate`,`v-once`)
- 3、vue-router 守衛和路由懶加載
- 4、Vue 渲染函數
- 5、Vue 的 `mixin`
- 6、Vuex 模塊
- 7、相關原理
- Vue.js 運行機制
- 響應式系統的基本原理
- 響應式系統的依賴收集追蹤原理
- 實現 Virtual DOM
- template 模板是怎樣通過 Compile 編譯的
- 數據狀態更新時的差異 diff 及 patch 機制
- 批量異步更新策略及 nextTick 原理
- Vuex 狀態管理的工作原理
- 七、React 高級
- 1、React 組件間信息傳遞
- 2、React JSX 原理
- 3、React 組件間數據共享(props, PropTypes, Context, Redux, Mobx)
- 4、React 路由加載
- 5、Context 的使用
- 6、Redux遵循的三個原則是什么?
- 7、`immutable` (什么是`immutable`, 為什么要使用,重要的API)
一、HTML+CSS 系列
1、布局的三種模式
-
彈性盒布局 flex
盒子是并列的,可以設置指定寬度,輕松實現兩欄,三欄布局。
但是,flexbox 布局方式對瀏覽器的支持不太友好,有一些兼容性問題,但是,這應該是未來發展的趨勢。 -
浮動布局 float
float 布局是目前各大網站用的最多的一種布局方式了。
通過給元素設置float屬性來實現元素浮動,浮動的元素是脫離文檔流的,但是不脫離文本流。特點:
- 對自身:
- float 元素可以形成塊,可以讓行內元素變成塊元素,也擁有寬和高;
- 浮動元素的位置盡量靠上;
- 設置 float:left 或 float:right,如果這一行滿足不了浮動元素的寬度,則會被擠到下一行。
- 對兄弟元素:
- 不影響其他塊元素的位置,但影響其他塊元素的文本。
- 對父元素:
- 造成高度塌陷
- 解決高度塌陷的方法:給父元素添加 overflow:hidden;。overflow:auto/scroll 或者 display:flex/inline-flex/table 等,也都可以解決高度塌陷,也是因為觸發了父元素為 BFC
- 詳細講解:BFC深層解讀
- 造成高度塌陷
- 對自身:
-
響應式布局
- 最簡單的方式是加上一個 meta 標簽:<meta name="viewport" content="width=device-width, initial-scale=1">
- 其中 width = device-width 這一句的意思是讓頁面的寬度等于屏幕的寬度。
- rem 是指 html 的 font-size 的大小, 根據 rem 來計算各個元素的寬高,然后在配合 media query 就可以實現自適應。
- @media query 語法
- 最簡單的方式是加上一個 meta 標簽:<meta name="viewport" content="width=device-width, initial-scale=1">
2、移動端布局適配設備的方案
- rem 布局
- rem 以根元素字體大小作為參照的布局方式,可以實現等比縮放布局,不管內容是多大的,顯示的內容是一樣的。
- 百分比布局 %
- 頁面寬度為100%,高度固定,當屏幕越大,顯示的內容也就越多。
- 特點:控件彈性,圖片等比例縮放,文字流式
3、1px邊框的解決方案
參考文章:移動端 1px 解決方案(完整版)
- 原因:
在高分辨率的顯示屏中,像素比dpr為2或者3,1px邊框看起來比真實的1px邊框看起來更寬 - 方案:
- 偽類 + transform:把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border,并 transform 的 scale 縮小0.5 或者 0.33,原先的元素相對定位,新做的 border 絕對定位。
- 根據像素比值 (dpr => device-pixel-ratio) 設置縮放比例:
- 當 dpr > 2.5 時設置 scale(0.333)
- 當 2.49 > dpr > 1.5 時設置 scale(0.5)
- 當 dpr < 1.49 時設置 scale(1)
- 此方式,所有場景都能滿足,支持圓角(偽類和本體類都需要加 border-radius)。但是,代碼量也很大,對于已經使用偽類的元素,可能需要多層嵌套。
- 根據像素比值 (dpr => device-pixel-ratio) 設置縮放比例:
- viewport + rem:
- 根據 meta 設置對應 viewport 的 rem 基準值,這種方式就可以像以前一樣輕松愉快的寫1px了。
- 此方式,所有場景都能滿足,一套代碼,可以兼容基本所有布局。但是,老項目修改代價過大,只適用于新項目。
- border-image
- 通常手機端的頁面設計稿都是放大一倍的,如:為適應iphone retina,設計稿會設計成750*1334的分辨率,圖片按照2倍大小切出來,在手機端看著就不會虛化,非常清晰。同樣,在使用 border-image 時,將 border 設計為物理1px
- background-image
- 偽類 + transform:把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border,并 transform 的 scale 縮小0.5 或者 0.33,原先的元素相對定位,新做的 border 絕對定位。
4、BFC(Block Formatting Contexts)
答案見:BFC(Block Formatting Context)塊級格式化上下文之深層解讀
5、BootStrap布局原理
- Bootstrap 是一個用于快速構建Web應用程序和網站的前端框架,是基于html、css、js。
- 柵格布局:
Bootstrap 內置了一套響應式、移動設備優先的流式柵格系統,會隨著屏幕設備或者視口(Viewport)尺寸的增加,系統會自動分為最多12列。
網格系統的實現原理也是非常簡單的,只需要通過定義容器的大小,然后平分12份(也有24,32的,但是12是最常見的),再調整內外邊距,最后結合媒體查詢,就可以實現了這種柵格布局系統。 - 優點:自適應布局,友好的移動端布局
- 缺點:不兼容IE,需要修改bootstrap的樣式
- 相關問題:
- 分辨率:xs(超小屏幕手機 < 780px)、sm(小屏幕平板 >= 780px)、md(中等屏幕桌面顯示器 >= 992px)、lg(大屏幕大桌面顯示器 >= 1200px)
- bootstrap 柵格還有24格的 要怎么設置:分別在十二的基礎上嵌套,每個嵌套一個!
- container 與 container-fluid 有什么區別:container是文本居中、container-fluid占整行
6、Sass/Less/Stylus區別,聯系,在什么場景下使用
詳細講解:【面試總結系列】CSS 預編譯器 Sass、Less、Stylus 三者之間的比較詳解
- CSS預處理器(Sass/less/stylus):就是一種專門的編程語言,為css增加一些編程的特性,將css作為目標生成文件,然后就可以使用這種語言進行編程。可以讓代碼更加簡潔、適應性更強、可讀性更強、易于代碼的維護。
- 區別與聯系:
見如上文章詳解。 - 使用場景:
- Sass:在編寫代碼時,需要用到變量、嵌套、混合、導入等高級操作時,可以更好的組織管理樣式文件,更高效的開發項目。
- Less:它更適用于皮膚、模板等整體框架固定死的網站制作,比如論壇、空間
- Stylus:可編程
- StyledComponents:在React中使用,編寫實際的css代碼來設計組件樣式,不需要組件和樣式之間的映射,創建成功后就是一個React組件
二、JavaScript 系列
1、變量對象 + 作用域鏈 + this(執行上下文)
詳細講解:深入理解JavaScript的函數作用域
-
變量對象:如果變量與執行上下文相關,那變量自己應該知道它的數據存儲在哪里,并且知道如何訪問,這種機制就是變量對象(VO)。
變量對象是一個與執行上下文相關的特殊對象,它存儲著在上下文中聲明的變量、函數聲明、函數形參等內容。
-
全局對象:是在進入任何執行上下文之前就已經創建了的對象。
全局對象只存在一份,它的屬性在程序中任何地方都可以訪問,全局對象的生命周期終止于程序退出那一刻。
-
作用域鏈與原型鏈:
詳細講解:
【JavaScript高級】原型與原型鏈- 區別:
1.作用域鏈是對于變量而言,原型鏈是對于對象的屬性。
2.作用域鏈頂層是window,原型鏈頂層是Object。 - 聯系:從鏈表開始查找,直到找到為止。
- this指向:取決于被調用的方式:
詳細講解:瀏覽器 JavaScript 中的 this 的使用
- 區別:
- 如果普通的函數調用,非嚴格模式下,this 指向 window,嚴格模式下,this 是 undefined;
- 如果是對象調用的方式,this 指向該對象;
- 如果是call()、apply() 或者 bind() 方式調用,this 指向被綁定的對象;
- 如果是構造函數調用方式,this 指向實例化出來的新對象;
- 如果是箭頭函數,是根據當前的詞法作用域來決定 this,具體來說,箭頭函數會繼承外層函數調用的 this 綁定。
2、閉包
- 閉包:就是能夠讀取其他函數內部變量的函數
- 變量的作用域分為全局變量和局部變量兩種。而 js 中的函數內部可以直接讀取全局變量,但是函數外部無法讀取函數內部的局部變量。如果想要取得函數內部的變量,就要在函數的內部再定義一個函數,將函數作為返回值返回,就可以在函數的外部讀取他的內部變量了。
- 閉包的作用:
- 可以讀取函數內部的變量
- 讓這些變量的值始終保存在內存中 => 可能導致內存泄漏
- 閉包的 this 指向:外部函數的 this 指向調用他的對象,內部函數的 this 指向了全局對象。
3、原型與原型鏈
- 原型(__proto__對象屬性):原型為同一個構造函數new出來的實例對象提供了一個公共的區域來存放共同的屬性和方法。
- js規定,每一個函數都有一個 prototype 對象屬性,指向另一個對象,prototype 的所有屬性和方法,都會被構造函數的實例繼承。這就意味著,我們可以把那些不變(公共)的屬性和方法,直接定義在 prototype 對象屬性上。prototype 就是調用構造函數所創建的那個實例對象的原型。
- prototype 可以讓所有的對象實例共享它所包含的屬性和方法。也就是說,不必再構造函數中定義對象信息,而是可以直接將這些信息添加到原型中。
- 為什么要使用原型:可以節省一定的內存,特別是需要構建多個實例的時候。
- 原型鏈:實例對象與原型之間的連接,叫做原型鏈。
- js在創建對象的時候,都有一個叫做 __proto__ 的內置屬性,用于指向創建它的函數對象的原型對象 prototype。
- 內部原型(__proto__)和構造器原型(prototype)
- 每個對象都有一個 __proto__ 屬性,原型鏈上的對象正是依賴這個屬性連結在一起。
- 作為一個對象,當訪問其中的一個屬性或者方法的時候,如果這個對象中沒有這個方法或屬性,那么js引擎將會訪問這個對象的 __proto__ 屬性所指向的上一個對象,并在那個對象中查找指定的方法或屬性,如果不能找到,那就會繼續通過這個對象的 __proto__ 屬性指向的對象進行向上查找,直到這個鏈表結束。
4、繼承
-
前提:提供父類(繼承誰,提供誰的屬性)
-
分類:
1、原型鏈繼承:可以讓新實例的原型等于父類的實例
- 特點:實例可繼承的屬性有:實例的構造函數的屬性、父類構造函數屬性、父類原型的屬性。(新實例不會繼承父類實例的屬性)
- 缺點:
1. 新實例無法向父類構造函數傳參
2. 繼承單一
3. 所有新實例都會共享父類實例的屬性(原型上的屬性是共享的,一個實例修改了原型屬性,另一個實例的原型屬性也會被修改)
2、構造函數繼承:用 .call() 和 .apply() 將父類構造函數引入子類函數(在子類函數中做了父類的復制)
- 特點:
1. 只繼承了父類構造函數的屬性,沒有繼承父類原型的屬性
2. 解決了原型鏈繼承的缺點
3. 可以繼承多個構造函數屬性(call多個)
4. 在子實例中可以向父實例傳參 - 缺點:
1. 只能繼承父類構造函數的屬性
2. 無法實現構造函數的復用(每次用都要重新調用)
3. 每個新實例都有父類構造函數的副本
3、組合繼承(原型鏈繼承 + 構造函數繼承):結合了兩種模式的優點,傳參和復用
- 特點:
1. 可以繼承父類原型上的屬性,可以傳參,可復用
2. 每個新實例引入的構造函數屬性是私有的 - 缺點:調用了兩次父類構造函數(耗內存),子類的構造函數會代替原型上的那個父類構造函數。
4、原型式繼承:用一個函數包裝一個對象,然后返回這個函數的調用,這個函數就變成了一個可以隨意增添屬性的實例或對象,Object.create() 就是這個原理
- 特點:類似于復制一個對象,用函數來包裝
- 缺點:
1. 所有實例都會繼承原型上的屬性
2. 無法實現復用(新實例屬性都是后面添加的)
5、寄生式繼承:就是給原型式繼承外面套個殼子
- 特點:沒有創建自定義類型,因為只是套了個殼子返回對象,這個函數就成了創建的新對象。
- 缺點:沒有用到原型,無法復用
6、寄生組合繼承:(常用)修復了組合繼承的問題
- 寄生:在函數內返回對象然后調用
- 組合:
1. 函數的原型等于另一個實例;
2. 在函數中用 apply 或者call引用另一個構造函數,可傳參
5、函數節流和防抖
- 函數的節流和防抖是優化高頻率執行js代碼的一種手段,js中的一些事件在執行觸發時,會不斷調用 綁定在事件上的回調函數,極大的浪費資源,降低性能。為了優化體驗,需要對這類事件進行調用次數的限制,此時引入函數節流防抖。
- 節流(throttle):控制事件發生的頻率,比如控制為1s發生一次,甚至1分鐘發生一次。
- 應用場景:
- scroll事件,滾動監聽事件,每隔一段時間計算一次位置信息等
- 瀏覽器的播放事件,每隔1s計算一次進度信息
- input框實時搜索并發送請求展示下拉列表,每隔1s發送一次請求。(防抖也可以)
- 高頻點擊提交,表單重復提交
- 代碼實現: function throttle(fn, delay) {let timer;return function () {let _this = this;let args = arguments;if (timer) {return;}timer = setTimeout(function () {fn.apply(_this, args);timer = null; // 在delay后執行完fn之后清空timer,此時timer為假,throttle觸發可以進入計時器}, delay)}}
- 防抖(debounce):防止抖動,以免把一次事件誤執行多次,影響性能。
- 應用場景:
- 登錄注冊、發短信等按鈕避免用戶點擊過快,導致多次發送請求,需要防抖
- 調整瀏覽器窗口大小時,resize次數過于頻繁,造成計算過多,此時需要一次到位,需要防抖
- 文本編輯器實時保存,無任何更改操作一段時間后自動保存
- mousemove、mouseover鼠標移動事件防抖
- 搜索框搜索輸入,只需要用戶最后一次輸入完,在發送請求防抖
- 手機號、郵箱驗證輸入檢測
- 代碼實現: function debounce(fn, delay) {let timer; // 維護一個 timerreturn function () {let args = arguments;if (timer) {clearTimeout(timer);}timer = setTimeout(()=> {fn.apply(this, args); // 用apply指向調用debounce的對象,相當于this.fn(args);}, delay);}; }
- 比較:
- 相同點:
- 都可以通過使用 setTimeout 實現
- 目的都是為了降低回調執行頻率,節省計算資源
- 不同點:
- 函數防抖:在一段連續操作結束后,處理回調,利用 clearTimeout 和 setTimeout 實現。
- 函數節流:在一段連續操作中,每一段時間只執行一次,頻率較高的事件中使用它來提高性能。
- 函數防抖關注一定時間連續觸發的事件只在最后執行一次,而函數節流側重于一段時間內只執行一次。
- 總結:
- 防抖:防止抖動,單位時間內事件觸發會被重置,避免事件被誤傷觸發多次。代碼實現重在清零 clearTimeout。防抖可以比作等電梯,只要有人進來,就需要再等一會。業務場景有避免觸發按鈕多次重復提交。
- 節流:控制流量,單位時間內事件只能觸發一次。代碼實現重在開鎖關鎖 timer = timeout; timer = null。節流可以比作紅綠燈,每等一個紅燈時間就可以過一批。
6、函數柯里化與反柯里化
- 柯里化(currying):是一種編程技術,主要就是把原本接收多個參數的函數變成只接受一個參數的函數,并且返回一個接收剩余參數的函數。
- 應用場景:
1. 封裝一些含有環境判斷的方法時,函數柯里化可以幫助我們減少判斷條件執行的次數;
2. 在封裝函數節流、防抖、bind此類返回值是函數的方法時,會用到函數柯里化;
3. 可用與 vue、react、小程序中的事件傳參,在事件綁定時就執行回調函數傳參,根據傳參值返回真正的事件 callback函數。
- 應用場景:
- 反柯里化:擴大方法的適用范圍。
1. 可以讓任何對象擁有其他對象的方法(改變原來方法上下文)
2. 增加被反柯里化方法接收的參數
7、設計模式
設計模式是一套被反復使用的、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。
工廠模式定義了一個用于創建對象的接口,用戶只負責傳遞需要的參數,不需要關心內部的邏輯,隱藏了創建實例的復雜度,最后返回一個實例。
單例模式是指在內存中只會創建且僅創建一次對象的設計模式。單例模式很常用,比如全局緩存、全局狀態管理等,這些只需要一個對象,就可以使用單例模式。比如 Redux、Vuex的store。
觀察者模式中一般都需要實現三個接口: subscribe()接收觀察者,使其訂閱;unsubscribe()取消訂閱;fire()觸發事件,通知到所有觀察者。
裝飾器模式不需要改變已有的接口,它的作用是給對象添加功能。比如 react里的高階組件。
適配器用來解決兩個接口不兼容的問題,不需要改變已有的接口,通過包裝一層的方式實現兩個接口的正常協作。我們其實經常使用到適配器模式。比如父組件傳遞給子組件一個屬性,組件內部需要使用 computed計算屬性來做處理,這個過程就用到了適配器模式。
8、ES6新增API(Proxy, Reflect, Promise, Generator, async, Decorator, Class)
-
Proxy:用于修改某些操作的默認行為,也可以理解為在目標對象之前架設一層攔截,外部所有的訪問都必須先通過這層攔截,因此提供了一種機制,可以對外部的訪問進行過濾和修改。這個詞的原理為代理,在這里可以表示由它來“代理”某些操作,譯為“代理器”。
- 基本用法:ES6原生提供給了Proxy構造函數,用來生成Proxy實例。var proxy = new Proxy(target, handler); Proxy對象的所有用法,都是通過這種形式。不同的只是handle參數的寫法。其中new Proxy用來生成Proxy實例,target是表示所要攔截的對象,handle是用來定制攔截行為的對象。
-
Reflect:是一個全局的普通的對象,原型是Object。
- 目的:
1. 將Object對象的一些屬于語言內部的方法放到Reflect對象上,從Reflect上能拿到語言內部的方法。如:Object.defineProperty;
2. 修改某些object方法返回的結果。如:Object.defineProperty(obj, name, desc)在無法定義屬性的時候會報錯,而Reflect.defineProperty(obj, name, desc)則會返回false;
3. 讓Object的操作都變成函數行為。如object的命令式:name in obj和delete obj[name] 則與 Reflect.has(obj, name)、Reflect.deleteProperty(obj, name)相等;
4. Reflect對象的方法與Proxy對象的方法一一對應,只要proxy對象上有的方法reflect也能找到。
- 目的:
-
Promise:是一個專門解決異步回調地獄的問題。
-
所謂 Promise,簡單點來說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果,promise是一個對象,它可以從獲取異步操作的消息,promise提供了統一的API,各種異步操作都可以用同樣的方法進行處理。
- 特點:
- 對象的狀態不受外界影響,promise對象代表一個異步操作,有三種狀態:pending(進行中)、fulfilled(已成功)、rejected(失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。
- 一旦狀態改變就不會再變,任何時候都可以得到這個結果,promise對象的狀態改變,只有兩種可能:從 pending => fulfilled,從pending => rejected。這個就稱為 resolved。如果改變已經發生,再對 promise 對象添加回調函數,也會立即得到這個結果,這與事件(event)完全不同,事件的特點是:如果你錯過了它,再去監聽是得不到結果的。
- 用法:
- Promise 是一個構造函數,這個構造函數有兩個參數,分別是 resolve(成功之后的回調函數)和 reject(失敗之后的回調函數)
因為 Promise 表示的是一個異步操作,每當我們 new 一個 promise 實例,就表示一個具體的異步操作,那么這個異步操作的結果就只能有兩種狀態:成功/失敗,兩者都需要回調函數 resolve/reject返回。所以內部拿到操作結果后,無法使用 return 把操作結果返回給調用者,這個時候只能用回調函數的形式來吧成功或者失敗的結果返回給調用者。 - promise 實例生成以后,可以用 then方法分別指定 resolved 狀態和 rejected 狀態的回調函數,then方法可以接收兩個回調函數作為參數,第一個回調函數是 promise 對象的狀態變成 resolved 時調用,第二個回調函數是 promise 對象的狀態變為 rejected 時調用。第二個回調函數是可選的,這兩個函數都接受 promise 對象傳出的值作為參數。
-
Generator:與平常的函數不同,它可以理解為是一個分布執行的函數,返回值是一個遍歷器。
- 用法:
1. 外部可以通過next(),thow()和return()調用,只是調用的形式不同。
2. 在應用方面主要是異步調用,不同于以前的回調函數和Promise(Promise算是對回調函數解決嵌套繁瑣問題提出的)
3. 它在每一個yield中部署自己的異步操作,等到需要執行的時候再調用。
4. generator函數和Ajax可以一起進行同步操作。
5. 它的分布執行的特性決定了它對耗時大的多步操作有很大的改進(generator如果你不執行,那之后的程序系統不會編譯)。
6. 部署Iterator接口:generator函數可以再任何對象上部署Iterator接口。
async/await:回調地獄的終極解決方案,使用它可以把異步代碼寫的看起來像同步代碼。
- await 是一個函數中的關鍵字,要求函數必須是 async 聲明的函數。當 await 中的方法執行完畢或者返回后執行后續代碼。
Decorator:修飾器,是一個函數,用來修飾類的行為。不過目前主流瀏覽器都沒有很好的支持,我們需要用babel來轉換為瀏覽器能識別的語言。
- 裝飾器在javascript 中僅僅可以修飾類和屬性,不能修飾函數。
- 裝飾器對類的行為的改變,是代表編譯時發生的,而不是在運行時。
- 裝飾器能在編譯階段運行代碼。
- 裝飾器是經典的AOP模式的一種實現方式。
- 執行順序:同一處的多個裝飾器是按照洋蔥模型,由外到內進入,再由內到外執行。
Class:類,通過class關鍵字可以定義類。
- class 關鍵字的出現使得其在對象寫法上更加清晰,更像是一種面向對象的語言。
- 注意:
1. 在類中聲明方法的時候,千萬不要給該方法加上 function 關鍵字
2. 方法之間不要用逗號分割,否則會報錯 - 用法:
1. 類自身指向的就是構造函數,所以可以認為 ES6 中的類其實就是構造函數的另一種寫法。
2. 類的所有方法都定義在類的prototype屬性上,也可以通過prototype屬性對類添加方法。
3. 可以通過 Object.assign() 來為對象動態增加方法。
4. constructor方法是類的構造函數的默認方法,通過new命令生成對象實例時,自動調用該方法。
5. constructor方法如果沒有顯式定義,會隱式生成一個constructor方法。所以即使你沒有添加構造函數,構造函數也是存在的。constructor方法默認返回實例對象this,但是也可以指定constructor方法返回一個全新的對象,讓返回的實例對象不是該類的實例。
6. 類的所有實例共享一個原型對象,他們的原型都是 Person.prototype,所以proto屬性是相等的。
7. class不存在變量提升,所以需要先定義再使用。
9、簡述ES6的新特性
JavaScript ES6 箭頭函數與普通函數的區別詳解【面試必備,值得收藏】
- 不一樣的變量聲明:const和let
- 模板字符串
- 箭頭函數(Arrow Functions)
- 函數的參數默認值
- Spread / Rest 操作符
- 二進制和八進制字面量
- 對象和數組解構
- 對象超類
- for…of 和 for…in
- ES6中的類 Class
10、瀏覽器渲染頁面的過程
從瀏覽器地址欄輸入url到顯示頁面的步驟【超詳細講解】
-
瀏覽器從服務器那收到的HTML,CSS,JavaScript等相關資源,然后經過一系列處理后渲染出來的web頁面。
-
過程:
- 瀏覽器將獲取的HTML文檔并解析成DOM樹。
- 處理CSS標記,構成層疊樣式表模型CSSOM(CSS Object Model)。
- 將DOM和CSSOM合并為渲染樹(rendering tree)將會被創建,代表一系列將被渲染的對象。
- 渲染樹的每個元素包含的內容都是計算過的,它被稱之為布局layout。瀏覽器使用一種流式處理的方法,只需要一次pass繪制操作就可以布局所有的元素。
- 將渲染樹的各個節點繪制到屏幕上,這一步被稱為繪制painting。
以上五個步驟并不一定一次性順序完成,比如DOM或CSSOM被修改時,亦或是哪個過程會重復執行,這樣才能計算出哪些像素需要在屏幕上進行重新渲染。而在實際情況中,JavaScript和CSS的某些操作往往會多次修改DOM或者CSSOM。
11、瀏覽器緩存(強緩存,協商緩存)
- 緩存:就是一個資源副本,當我們向服務器請求資源后,會根據情況將資源copy一份副本存在本地,方便下次讀取。緩存最根本的作用是減少沒必要的請求,使用緩存可以減少時長,從而優化用戶體驗,減少流量消耗,減輕服務器的壓力。
- 強緩存:直接從本地副本比對讀取,不去請求服務器,返回的狀態碼是 200(expires 和 cache-control)
- 協商緩存:會去服務器比對,若沒改變才直接讀取本地緩存,返回的狀態碼是 304(last-modified 和 etag)
- 獲取緩存的流程:
1. 先根據這個資源的 http header 判斷它是否命中強緩存,如果命中,則直接從本地緩存中獲取資源,不會則向服務器請求 資源。
2. 當強緩存沒有命中時,客戶端會發送請求到服務器,服務器通過另一些request header驗證這個資源是否命中協商緩存,這個過程成為http再驗證,如果命中,服務器直接返回請求而不返回資源,而是告訴客戶端之間從緩存中獲取,客戶端收到返回后就直接從客戶端獲取資源。
3. 強緩存和協商緩存的共同之處在于:如果命中緩存,服務器不會返回資源;區別是:強緩存不發送請求打服務器,但是協商緩存會發送請求到服務器。
4. 當協商緩存沒有命中時,服務器會返回資源給客戶端。
5. 當ctrl+F5強制刷新網頁時,直接從服務器加載,跳過強緩存和協商緩存。
6. 當F5刷新頁面時,跳過強緩存但會檢查協商緩存
12、瀏覽器端 Event loop
https://segmentfault.com/a/1190000018181334
- 事件循環(event-loop):主線程從"任務隊列"中讀取執行事件,這個過程是循環不斷的,這個機制被稱為事件循環。此機制具體如下:主線程會不斷從任務隊列中按順序取任務執行,每執行完一個任務都會檢查microtask隊列是否為空(執行完一個任務的具體標志是函數執行棧為空),如果不為空則會一次性執行完所有microtask。然后再進入下一個循環去任務隊列中取下一個任務執行。
- 什么需要:因為JavaScript是單線程的。單線程就意味著,所有任務需要排隊,前一個任務結束,才會執行后一個任務。如果前一個任務耗時很長,后一個任務就不得不一直等著。為了協調事件(event),用戶交互(user interaction),腳本(script),渲染(rendering),網絡(networking)等,用戶代理(user agent)必須使用事件循環(event loops)。
13、路由實現原理
-
早期的路由都是后端實現的,直接根據 url 來 reload 頁面,頁面變得越來越復雜服務器端壓力變大,隨著 ajax 的出現,頁面實現非 reload 就能刷新數據,也給前端路由的出現奠定了基礎。我們可以通過記錄 url 來記錄 ajax 的變化,從而實現前端路由。
-
History API(history.pushState 和 history.replaceState)
- 這兩個API都接收三個參數,分別是
狀態對象(state object) — 一個JavaScript對象,與用pushState()方法創建的新歷史記錄條目關聯。無論何時用戶導航到新創建的狀態,popstate事件都會被觸發,并且事件對象的state屬性都包含歷史記錄條目的狀態對象的拷貝。
標題(title) — FireFox瀏覽器目前會忽略該參數,雖然以后可能會用上。考慮到未來可能會對該方法進行修改,傳一個空字符串會比較安全。或者,你也可以傳入一個簡短的標題,標明將要進入的狀態。
地址(URL) — 新的歷史記錄條目的地址。瀏覽器不會在調用pushState()方法后加載該地址,但之后,可能會試圖加載,例如用戶重啟瀏覽器。新的URL不一定是絕對路徑;如果是相對路徑,它將以當前URL為基準;傳入的URL與當前URL應該是同源的,否則,pushState()會拋出異常。該參數是可選的;不指定的話則為文檔當前URL。 - 相同之處是這兩個API都會操作瀏覽器的歷史記錄,不會引起頁面的刷新。
- 不同之處是pushState會增加一條新的歷史記錄,而replaceState會替換當前的歷史記錄。
- 這兩個API都接收三個參數,分別是
-
hash
- 我們經常在 url 中看到 #,這個 # 有兩種情況,一個是我們所謂的錨點,比如典型的回到頂部按鈕原理、Github 上各個標題之間的跳轉等,路由里的 # 不叫錨點,我們稱之為 hash,大型框架的路由系統大多都是哈希實現的。
- hashchange事件:根據監聽哈希變化觸發的事件
- 使用 window.location 處理哈希的改變時不會重新渲染頁面,而是當作新頁面加到歷史記錄中, 這樣跳轉頁面就可以在 hashchange事件中注冊 ajax 從而改變頁面內容。
- hashchange 在低版本 IE 需要通過輪詢監聽 url 變化來實現
14、session、cookie 與 token 認證
參考文檔:Cookie、Session和Token認證詳解
- 用戶通過瀏覽器登錄一個網站,在該瀏覽器內打開網站其他頁面時,不需要重新登錄。而HTTP是無狀態的協議,不同的網站,判斷用戶登錄狀態的方法都不一樣。有的網站是通過session來驗證用戶的登錄狀態,有的網站是通過token來驗證用戶的登錄狀態,也有的網站是通過其他的方式來判斷。
- Cookie 是服務器發送給客戶端的用于驗證某一會話信息的數據,不同網站Cookie中字段是不一樣的,是由服務器端設置的。Cookie中常放入session_id 或者 token 用來驗證會話的登錄狀態。
- session是保存在服務器端的經過加密的存儲特定用戶會話所需的屬性及配置信息的數據。當我們打開瀏覽器訪問某網站時,session建立,只要瀏覽器不關閉(也有時間限制,可以自己設置超時時間),這個網站就可以記錄用戶的狀態,當瀏覽器關閉時,session結束。
- Token是服務器端生成的用于驗證用戶登錄狀態的加密數據,和用session驗證差不多。只不過Token驗證服務器端不需要存儲用戶會話所需的配置等數據。只需要后端將Token進行驗證簽名,然后再發給瀏覽器。所以,使用Token進行驗證,在一次會話中,Token值是不變化的,這和session一樣。
15、跨域的解決方案
參考文檔:前端常見瀏覽器跨域請求解決方案
- jsonp 跨域
動態創建script,再請求一個帶參網址實現跨域通信(script 標簽的 src 屬性不受同源策略的限制 );
缺點是只能實現 get 一種請求。 - CORS(跨域資源共享)
在后端添加允許訪問的請求頭
js // 配置 cors 跨域 header("Access-Control-Allow-Origin:*"); header("Access-Control-Request-Methods:GET, POST, PUT, DELETE, OPTIONS"); header('Access-Control-Allow-Headers:x-requested-with,content-type,test-token,test-sessid'); - nginx 代理(proxy)
正向代理,反向代理 - nodejs 中間件代理
node中間件實現跨域代理,原理大致與nginx相同,都是通過啟一個代理服務器,實現數據的轉發
使用 node + express + http-proxy-middleware 搭建一個proxy服務器。
16、Ajax (axios、fetch、原生xhr)
-
原生xhr:XMLHttpRequest對象
- 現代瀏覽器,最開始與服務器交換數據,都是通過XMLHttpRequest對象。
- 優點:
1. 不重新加載頁面的情況下更新網頁
2. 在頁面已加載后從服務器請求/接收數據
3. 在后臺向服務器發送數據。 - 缺點:
1. 使用起來也比較繁瑣,需要設置很多值。
2. 早期的IE瀏覽器有自己的實現,這樣需要寫兼容代碼。 // 兼容處理if (window.XMLHttpRequest) { // model browserxhr = new XMLHttpRequest()} else if (window.ActiveXObject) { // IE 6 and olderxhr = new ActiveXObject('Microsoft.XMLHTTP')}xhr.open('POST', url, true)xhr.send(data)xhr.onreadystatechange = function () {try {// TODO 處理響應if (xhr.readyState === XMLHttpRequest.DONE) {// XMLHttpRequest.DONE 對應值是 4// Everything is good, the response was received.if (xhr.status === 200) {// Perfect!} else {// There was a problem with the request.// For example, the response may hava a 404 (Not Found)// or 500 (Internal Server Error) response code.}} else {// Not ready yet}} catch (e) {// 通信錯誤的事件中(例如服務器宕機)alert('Caught Exception: ' + e.description)}}
-
jQuery ajax: $.ajax
- jQuery 里面的 AJAX 請求也兼容了各瀏覽器,可以有簡單易用的方法.get,.get,.get,.post。簡單點說,就是對XMLHttpRequest對象的封裝。
- 優點:
1. 對原生XHR的封裝,做了兼容處理,簡化了使用。
2. 增加了對JSONP的支持,可以簡單處理部分跨域。 - 缺點:
1. 如果有多個請求,并且有依賴關系的話,容易形成回調地獄。
2. 本身是針對MVC的編程,不符合現在前端MVVM的浪潮。
3. ajax是jQuery中的一個方法。如果只是要使用ajax卻要引入整個jQuery非常的不合理。$.ajax({type: 'POST',url: url, data: data,dataType: dataType,success: function () {},error: function () {} })
-
Axios
- Axios 是一個基于 promise 的HTTP庫,可以用在瀏覽器和 node.js 中。它本質也是對原生XMLHttpRequest的封裝,只不過它是Promise的實現版本,符合最新的ES規范。
- 優點:
1. 從瀏覽器中創建XMLHttpRequests
2. 從 node.js 創建 http 請求
3. 支持 Promise API
4. 攔截請求和響應
5. 轉換請求數據和響應數據
6. 取消請求
7. 自動轉換 JSON 數據
8. 客戶端支持防御 XSRF - 缺點:
只持現代代瀏覽器。 axios({method: 'post',url: '/user/12345',data: {firstName: 'liu',lastName: 'weiqin'}}).then(res => console.log(res)).catch(err => console.log(err))
-
fetch
- Fetch API提供了一個 JavaScript 接口,用于訪問和操作HTTP管道的部分,例如請求和響應。它還提供了一個全局fetch()方法,該方法提供了一種簡單,合理的方式來跨網絡異步獲取資源。
- fetch是低層次的API,代替XHR,可以輕松處理各種格式,非文本化格式。可以很容易的被其他技術使用,例如Service Workers。但是想要很好的使用fetch,需要做一些封裝處理。
- 優點:
在配置中,添加mode: 'no-cors’就可以跨域了 - 問題:
1. fetch只對網絡請求報錯,對400,500都當做成功的請求,需要封裝去處理
2. fetch默認不會帶cookie,需要添加配置項。
3. fetch不支持abort,不支持超時控制,使用setTimeout及Promise.reject的實現超時控制并不能阻止請求過程繼續在后臺運行,造成了流量的浪費。
4. fetch沒有辦法原生監測請求的進度,而XHR可以。fetch('http://example.com/movies.json') .then(function(response) {return response.json(); }) .then(function(myJson) {console.log(myJson); }); - fetch規范與jQuery.ajax()主要有兩種方式的不同:
- 當接收到一個代表錯誤的 HTTP 狀態碼時,從 fetch()返回的 Promise 不會被標記為 reject, 即使該 HTTP 響應的狀態碼是 404 或 500。相反,它會將 Promise 狀態標記為 resolve (但是會將 resolve的返回值的 ok 屬性設置為 false ),僅當網絡故障時或請求被阻止時,才會標記為 reject。
- 默認情況下,fetch 不會從服務端發送或接收任何 cookies, 如果站點依賴于用戶 session,則會導致未經認證的請求(要發送 cookies,必須設置 credentials 選項)。
三、webpack 系列
1、webpack 細節
- webpack中在使用babel插件處理js代碼的時候,為什么使用polyfill,如何使用polyfill?
- 原因:因為在使用preset_env 處理js代碼時,無法將所有的ES6的語法全部轉換成ES5語法,就比如promise、array.from以及實例方法都無法轉換,這個時候需要加入墊片。
- 使用:
1. 在入口文件引入@babel/polyfill ,會污染全局環境
2. 在配置文件中的entry中寫 ,也會污染全局環境
3. 可以配置@babel/preset-env useBuiltIns接收3個參數
entry:不管代碼 有沒有用到,只要目標瀏覽器不支持的都會引入對應的polyfill;自動引入polyfill模塊;
usage: 根據代碼的使用情況,按需引入;自動引入polyfill模塊;
false:不會自動引入polyfill模塊;
4. corejs 3.0以后的版本; 如果參數為entry,則需要在入口文件中引入兩個包
2、webpack 和 gulp 的區別
- webpack是一個模塊打包器,強調的是一個前端模塊化方案,更側重模塊打包,我們可以把開發中的所有資源都看成是模塊,通過loader和plugin對資源進行處理。
- gulp是一個前端自動化構建工具,強調的是前端開發的工作流程,可以通過配置一系列的task,第一task處理的事情(如代碼壓縮,合并,編譯以及瀏覽器實時更新等)。然后定義這些執行順序,來讓glup執行這些task,從而構建項目的整個開發流程。自動化構建工具并不能把所有的模塊打包到一起,也不能構建不同模塊之間的依賴關系。
3、webpack 從啟動構建到輸出結果經歷了一系列過程
4、gulp基本使用
四、nodejs 系列
1、Node.js的 Event loop
在進程啟動時,Node便會創建一個類似于while(true)的循環,每執行一次循環體的過程我們稱為Tick。每個Tick的過程就是查看是否有事件待處理。如果有就取出事件及其相關的回調函數。然后進入下一個循環,如果不再有事件處理,就退出進程。
2、Node中間件
-
中間件主要是指封裝所有Http請求細節處理的方法。一次Http請求通常包含很多工作,如記錄日志、ip過濾、查詢字符串、請求體解析、Cookie處理、權限驗證、參數驗證、異常處理等,但對于Web應用而言,并不希望接觸到這么多細節性的處理,因此引入中間件來簡化和隔離這些基礎設施與業務邏輯之間的細節,讓開發者能夠關注在業務的開發上,以達到提升開發效率的目的。
-
Koa 與 Express 比較
- 語法區別
Express 異步使用 回調
koa1 異步使用 generator + yeild
koa2 異步使用 await/async - 中間件區別
koa采用洋蔥模型,進行順序執行,出去反向執行,支持context傳遞數據
Express本身無洋蔥模型,需要引入插件,不支持context
Express的中間件中執行異步函數,執行順序不會按照洋蔥模型,異步的執行結果有可能被放到最后,response之前。
這是由于,其中間件執行機制,遞歸回調中沒有等待中間件中的異步函數執行完畢,就是沒有await中間件異步函數。 - 集成度區別
express 內置了很多中間件,集成度高,使用省心,
koa 輕量簡潔,容易定制
- 語法區別
3、Node三大框架:Express、Koa、Nest對比
- Express.js
- 規模小、比較靈活的一款開發框架;
- Node官方推薦,具有強大的Api;
- 支持許多其他軟件包和模塊引擎;
- 線性邏輯:路由和中間件完美融合,通過中間件形式把業務邏輯細分,簡化,一個請求進來經過一系列中間件處理后再響應給用戶,將復雜的業務線性化,清晰明了;
- 但是,Express 是基于 callback 來組合業務邏輯。Callback 有兩大硬傷,一是不可組合,二是異常不可捕獲。Express 的中間件模式雖然在一定程度上解決這兩個問題,但沒法徹底解決。
- 適合初學者學習,有強大的社區支持;
- Koa.js
- Express 的下一代,性能比較好;
- 采用ES6的語法,極大地提升錯誤處理的效率;
- 需要引用別人開發的中間件或者自己開發,然后再開發業務邏輯;
- 對于初學者來說不太友好;
- Nest.js
- 高效、可擴展性高;
- 支持ts編寫,兼容 express 中間件;
- 有依賴注入和模塊化的思想,類似于MVC架構;
- 上手成本較高,但后期維護與擴展會很方便;
- 提供了一套完整的解決方案,包含了認證、數據庫、路由、http狀態碼、安全、配置、請求等開箱即用的技術。
4、Node.js 模塊(CommonJS、模塊分類、模塊導出、模塊加載)
CommonJS
一個js文件就是一個模塊
模塊內所有的變量均為局部變量,不會污染全局
模塊中需要提供給其他模塊使用的內容需要導出
導出使用 exports.xxx = xxx 或 module.exports=xxx 或 this.xxx=xxx
其他模塊可以使用 require 函數導入
node 實現了 CommonJS 規范,在編寫模塊時,都有 require、exports、module三個預先定義好的變量可以使用。
require{} 函數的兩個作用:
1. 執行導入的模塊中的代碼;
2. 返回導入模塊中的接口;
模塊分類
- 例如:fs,http,path,url 模塊;
- 所有內置模塊,在安裝 node.js 的時候,就已經編譯成 二進制文件,可以直接加載運行(速度較快);
- 部分內置模塊,在 node.exe 這個進程啟動的時候就已經默認加載了,所以可以直接使用。
- 按文件后綴來分
- 如果加載時,沒有指定文件后綴名,那么,就按照 .js,.json,.node順序依次加載相應模塊
模塊導出
// 方式一:module.exports 導出 module.exports = {say: sayName } // 方式二:exports 導出 exports.say = sayName模塊加載過程
(1)如果傳入的為具體的文件名,例如:require('./test.js'),
直接根據給定的路徑去加載模塊,找到了就加載成功,找不到加載失敗
(2)如果傳入的不是具體的文件名,例如:require('./test')
第一步:根據給定的路徑,依次添加文件后綴 .js、.json、.node進行匹配,如果匹配不到,執行第二步
第二步:查找是否有 test 目錄(嘗試尋找 test 包)
找不到:加載失敗
找到了:依次在test目錄下查找package.json文件(找到該文件后嘗試找main字段中的入口文件)、index.js、index.json、index.node,找不到則加載失敗
(1)如果是核心模塊,就直接加載核心模塊
(2)不是核心模塊:從當前目錄開始,依次遞歸查找所有父目錄下的node_modules 目錄中是否包含相應的包,如果查找完畢磁盤根目錄依然沒有,則加載失敗
五、其他
1、http和應用編程相關的問題
參考文章:HTTP協議超級詳解
2、git及git工作流
3、數據庫(MySQL、MongoDB)
4、前后端模板(artTemplate、ejs)
六、Vue 高級
前端面試題之 熱門框架Vue 篇
前端面試題之 進階 Vue3.0 篇
1、Vue.js 動態組件與異步組件
- 動態組件:keep-alive
當在組件之間切換的時候,想要保持這些組件的狀態,以避免反復渲染導致的性能問題。重新創建動態組件的行為通常是非常有用的,我們希望那些標簽的組件實例能夠被在他們第一次被創建的時候緩存下來,為了解決這個問題,可以使用 <keep-alive> 元素將其動態組件包裹起來。這樣,失活的組件將會被緩存。 - 異步組件:
- 在大型應用中,我們可能需要將應用分割成小一些的代碼塊,并且只在需要的時候才從服務器上加載一個模塊。為了簡化, Vue允許以一個工廠函數的方式定義組件,這個工廠函數會異步解析這個組件定義。Vue 只有在這個組件需要被渲染的時候才會觸發該工廠函數,且會把結果緩存起來供未來重渲染。
- 這樣工廠函數會收到一個 resolve 回調,這個回調函數會在從服務器得到組件定義的時候被調用,也可以調用 reject(reason) 來表示加載失敗。Vue.component('async-webpack-example', function (resolve) {// 這個特殊的 `require` 語法將會告訴 webpack// 自動將你的構建代碼切割成多個包,這些包// 會通過 Ajax 請求加載require(['./my-async-component'], resolve) })
- 也可以在工廠函數中返回一個 Promise,使用動態導入Vue.component('async-webpack-example',// 這個動態導入會返回一個 `Promise` 對象。() => import('./my-async-component') )
- 當使用局部注冊的時候,也可以之間提供一個返回 promise 的函數new Vue({components: {'my-component': () => import('./my-async-component')} })
- 處理加載狀態(2.3.0新增)const AsyncComponent = () => ({// 需要加載的組件 (應該是一個 `Promise` 對象)component: import('./MyComponent.vue'),// 異步組件加載時使用的組件loading: LoadingComponent,// 加載失敗時使用的組件error: ErrorComponent,// 展示加載時組件的延時時間。默認值是 200 (毫秒)delay: 200,// 如果提供了超時時間且組件加載也超時了,// 則使用加載失敗時使用的組件。默認值是:`Infinity`timeout: 3000 })
2、處理邊界情況($root,$parent,$refs,依賴注入,循環引用,$forceUpdate,v-once)
-
訪問根實例 $root
所有的子組件都可以將這個實例作為一個全局 store 來訪問或使用。
this.$root.xxx 對于demo或者非常小型的有少量組件的應用來說比較方便,但是在絕大多數情況下,需要使用 Vuex 來管理應用的狀態。 -
訪問父級組件實例 $parent
可以用來從一個子組件訪問父組件的實例。可以在后期隨時觸達父級組件,以替代將數據以 prop 的方式傳入子組件的方式。
在絕大多數情況下,會使得應用更難調試和理解,尤其是變更了父級組件的數據的時候。
在需要向任意更深層級的組件提供上下文信息時推薦使用依賴注入。 -
訪問子組件實例或子元素 $refs
// 子組件定義ref屬性<base-input ref="usernameInput"></base-input>// 允許父組件給子組件輸入框獲取焦點this.$refs.usernameInput.focus()
給子組件添加一個 ref 屬性,賦予一個ID引用,就可以使用$refs來訪問這個子組件實例當 ref 和 v-for 一起使用的時候,得到的ref會包含對應數據源的這些子組件的數組。
$refs 會在組件渲染完成后生效,并且不是響應式的,應該避免在模板或計算屬性中訪問 $refs -
依賴注入:provide 和 inject
- provide:可以指定想要提供給后代組件的數據/方法
- inject:在任何后代組件中,通過 inject 選項接收指定的屬性
- 相比 $parent:這種用法可以在任意后代組件中訪問該定義的屬性,而不需要暴露整個組件實例。同時這些組件之間的接口是始終明確定義的,和props一樣。
- 可以把依賴注入看作一部分“大范圍有效的prop”,除了
- 祖先組件不需要知道那些后代組件使用它提供的property
- 后代組件不需要知道被注入的property來自哪里
- 負面影響:
- 將應用程序中的組件與它們當前的組織方式耦合起來,使重構變得更加困難;
- 提供的property是非響應式的;
-
程序化的事件偵聽器:
- $emit 可以被 v-on 偵聽
- 手動偵聽事件:
- $on(eventName , eventHandler) 偵聽一個事件
- $once(eventName , eventHandler) 一次性偵聽一個事件
- $off(eventName , eventHandler) 停止偵聽一個事件
-
循環引用
- 遞歸組件
組件是可以在自己的模板中調用自身,但是只能通過name選項來實現。
當使用Vue.component全局注冊一個組件時,這個全局的ID會自動設置為該組件的name選項。
使用不當,遞歸組件就可能導致無限循環。所以,使用遞歸調用是有條件性的。 - 組件之間的循環引用
- 遞歸組件
-
模板定義替代品
- 內聯模板
inline-template:添加這個屬性會將這個組件里面的內容作為模板,但是會讓模板的作用域變得難以理解,在組件內優先選擇 template選項 或者 <template>元素來定義模板。<my-component inline-template>// ... </my-component> - X-Template
在 <script> 元素中,并為其帶上 text/x-template 的類型,然后通過id將模板引用過去。// 定義模板 <script type="text/x-template" id="hello-world-template">// ... </script> // 引用模板 Vue.component('hello-world', {template: '#hello-world-template' })
- 內聯模板
-
控制更新
- 強制更新:$forceUpdate
- v-once 創建低開銷的靜態組件
組件內有大量靜態內容,可以在根元素上添加 v-once 來使這些內容只計算一次然后緩存起來
3、vue-router 守衛和路由懶加載
- 導航守衛分類(按維度)
beforeEach:全局前置守衛,進入路由前執行
beforResolve:全局解析守衛,在導航被確認之前,同時在所有組件內守衛和異步路由組件被解析之后,解析守衛就被調用
afterEach:全局后置守鉤子,導航確認執行時執行,可理解為導航完成時執行
beforeEnter:進入該路由前
beforeRouteEnter:進入組件時,不能獲取組件實例this,因為當守衛執行前,組件實例還沒被創建
beforeRouteUpdate:組件被復用時調用
beforeRouteLeave:離開組件時
-
導航解析流程
- 導航被觸發
- 在失活的組件里調用 beforeRouteLeave 守衛
- 調用全局的 beforeEach 守衛
- 在重用的組件里調用 beforeRouteUpdate守衛
- 在路由配置里調用 beforeEnter
- 解析異步路由組件
- 在被激活的組件里調用 beforeRouteEnter
- 調用全局的 beforeResolve 守衛
- 導航被確認
- 調用全局的afterEach鉤子
- 觸發 DOM 操作
- 調用 beforeRouteEnter守衛中傳給 next 的回調函數,創建好的組件實例會作為回調函數的參數傳入
-
路由懶加載
- resolve
這一種方法較常見。它主要是使用了resolve的異步機制,用require代替了import,實現按需加載。 - 官網方法
vue-router在官網提供了一種方法,可以理解也是為通過Promise的resolve機制。因為Promise函數返回的Promise為resolve組件本身,而我們又可以使用import來導入組件。 - require.ensure
這種模式可以通過參數中的webpackChunkName將js分開打包。
component: resolve => require.ensure([], () => resolve(require(’@/components/’+componentName)), ‘webpackChunkName’)
結合 Vue 的異步組件和 Webpack 的代碼分割功能,把不同路由對應的組件分割成不同的代碼塊,然后當路由被訪問的時候才加載對應組件
4、Vue 渲染函數
- Vue 一般使用 template 來創建HTML,有的時候需要使用 js 來創建 html,這個時候就要用到 render 渲染函數。
- 使用render: function(createElement) {return createElement('h' + this.level, // tag name 標簽名稱this.$slots.default // 組件的子元素存儲在組件實列 $slots.default 中。) },
- createElement
- Vue通過建立一個虛擬DOM對真實的DOM發生變化保存追蹤。
- return createElement(‘h1’, this.title);
- createElement 返回的是包含的信息,會告訴VUE頁面上需要渲染什么樣的節點及其子節點。稱這樣的節點為虛擬DOM(VNode)。
- data對象
- 在模板語法中,我們可以使用 v-bind:class 和 v-bind:style 來綁定屬性,在VNode數據對象中,下面的屬性名的字段級別是最高的。
- data對象允許我們綁定普通的html特性,就像DOM屬性一樣。
- 使用Javascript代替模板功能
- v-if 和 v-for
template 中有 v-if 和 v-for, 但是vue中的render函數沒有提供專用的API。在render函數中會被javascript的 if/else 和map重新實現。 - v-model
render函數中沒有 與 v-model相應的api,我們必須自己來實現相應的邏輯。
- v-if 和 v-for
- 插槽
可以從 this.$slots 獲取VNodes列表中的靜態內容 - 函數式組件
- 函數式組件我們標記組件為 functional, 意味著它無狀態(沒有data), 無實列(沒有this上下文)。
- 組件需要的一切通過上下文傳遞,包括(props、children、slots、data、parent、listeners、injections)
- 在添加 functional: true 之后,組件的 render 函數之間簡單更新增加 context 參數,this.$slots.default 更新為 context.children,之后this.level 更新為 context.props.level。
5、Vue 的 mixin
vue中提供了一種混合機制–mixins,用來更高效的實現組件內容的復用。
組件在引用之后相當于在父組件內開辟了一塊單獨的空間,來根據父組件props過來的值進行相應的操作,單本質上兩者還是涇渭分明,相對獨立。
而mixins則是在引入組件之后,則是將組件內部的內容如data等方法、method等屬性與父組件相應內容進行合并。相當于在引入后,父組件的各種屬性方法都被擴充了。
作用:多個組件可以共享數據和方法,在使用mixin的組件中引入后,mixin中的方法和屬性也就并入到該組件中,可以直接使用。鉤子函數會兩個都被調用,mixin中的鉤子首先執行。
6、Vuex 模塊
參考文章:Vuex模塊化應用實踐
流程圖
可以做異步操作,但是會違背 flux 思想
7、相關原理
Vue.js 運行機制
- 初始化及掛載
在 new Vue() 之后,Vue會調用 _init 函數進行初始化,也就是進行init 過程,它會初始化生命周期、事件、props、methods、data、computed與watch等。其中重要的是通過 Object.defineProperty 設置 setter 和 getter 函數,用來實現響應式和依賴收集。
初始化之后調用 $mount 會掛載組件,如果是運行時編譯,即不存在 render function ,但是存在 template 的情況,需要進行編譯的步驟。 - 編譯
compile 編譯可以分成 parse、optimize 與 generate 三個階段,最終需要得到render function。
parse:用正則解析 template 模板中的指令、class、style等數據,形成 AST(抽象語法樹);
optimize:用來標記靜態節點,將來更新視圖的時候,通過diff算法會跳過靜態節點,達到優化的一個目的;
generate:將AST轉換成 render function字符串,得到結果是 render 的字符串以及 staticRenderFns字符串。 - 響應式
當 render function 被渲染的時候,因為會讀取所需對象的值,所以會觸發 getter 函數進行依賴收集,依賴收集的目的的將觀察者 Watcher 對象存放到當前閉包中的訂閱者 Dep 的subs 中。
在修改對象的值的時候,會觸發對應的 setter,setter通知之前依賴收集得到的 Dep中的每一個 Watcher,告訴他們自己的值改變了,需要重新渲染視圖。這時候這些 Watcher 就會開始調用 update 來更新視圖。 - Virtual DOM(虛擬DOM節點)
Virtual DOM 其實就是一棵以 js 對象作為基礎的樹,用對象屬性來描述節點,實際上它只是一層對真實DOM的抽象。 - 更新視圖
patch() 方法對比新的 VNode 和 舊的 VNode。通過diff算法得到差異,進行對應修改。 - 流程圖
響應式系統的基本原理
響應式系統的依賴收集追蹤原理
實現 Virtual DOM
template 模板是怎樣通過 Compile 編譯的
數據狀態更新時的差異 diff 及 patch 機制
批量異步更新策略及 nextTick 原理
Vuex 狀態管理的工作原理
七、React 高級
1、React 組件間信息傳遞
使用 context 傳遞數據
利用 redux 傳遞數據
2、React JSX 原理
- JSX 是js的語法擴展,就像一個擁有js的全部功能的模板語言,在里面可以使用js代碼{},經過react語法的構造,編譯轉化,最后得到dom元素,插到頁面中。
- 實現原理:react封裝了createElement,可以構建一個js對象來描述HTML結構的信息。其中,第一個參數是標簽名,第二個參數是對象,包含了所有的屬性,第三個是子節點。
- 過程:JSX —— 使用react構造組件,bable編譯 => js對象 —— ReactDOM.render() => DOM 元素 => 插入頁面
3、React 組件間數據共享(props, PropTypes, Context, Redux, Mobx)
4、React 路由加載
- 路由組件的加載一共有三種方式,component\render\children
5、Context 的使用
context,通過createContext創建一個context,在所有組件的最外層包裹一個provider,然后通過給provider綁定數據以及方法,然后后代組件可以通過tatic contextType 或者consumer來獲取context里面的值,如果是consumer的話,那么就是使用一個回調函數,回調函數的參數就是context的值
6、Redux遵循的三個原則是什么?
7、immutable (什么是immutable, 為什么要使用,重要的API)
- 什么是immutable:
- Immutable Data 就是一旦創建,就不能再被更改的數據。對 Immutable 對象的任何修改或添加刪除操作都會返回一個新的 Immutable 對象。
- Immutable 實現的原理是 Persistent Data Structure(持久化數據結構),也就是使用舊數據創建新數據時,要保證舊數據同時可用且不變。
可以利用這個特性,去做時間旅行,也就是調用之前存在過的舊數據。 - 為了避免 deepCopy 把所有節點都復制一遍帶來的性能損耗,Immutable 使用了 Structural Sharing(結構共享),即如果對象樹中一個節點發生變化,只修改這個節點和受他影響的父節點,其他節點則進行共享。
- Immutable 優點:
- 降低 mutable 帶來的復雜度
- 節省內存
- 歷史追溯性(時間旅行):時間旅行指的是:每時每刻的值都被保留了,想回退到哪一步只要簡單的將數據取出就行,這個特性在 redux或者flux中特別有用。
- 函數式編程:Immutable 本來就是函數式編程的概念,純函數式編程的特點是:只要輸入一致,輸出必然一致,相比于面向對象,這樣開發組件和調試更方便。
- Immutable 缺點:
- 需要重新學習 api
- 資源包大小增加
- 容易與原生對象混淆:由于 api 與原生不同,混用的話容易出錯。
- Immutable API:
- fromJS():將一個js數據轉換為Immutable類型的數據
- toJS():將一個Immutable數據轉換為JS類型的數據
- is():對兩個對象進行比較
- 數據讀取:
- get() 、 getIn():獲取數據結構中的數據
- has() 、 hasIn():判斷是否存在某一個key
- includes():判斷是否存在某一個value
- first() 、 last():用來獲取第一個元素或者最后一個元素,若沒有則返回undefined
- 數據修改:這里對于數據的修改,是對原數據進行操作后的值賦值給一個新的數據,并不會對原數據進行修改,因為Immutable是不可變的數據類型。
- set():設置第一層key、index的值
- setIn():設置深層結構中某屬性的值
- delete():刪除第一層結構中的屬性
- update():對對象中的某個屬性進行更新,可對原數據進行相關操作
- clear():清除所有數據,clear(): this
文檔持續更新中。。。加油騷年!!
總結
以上是生活随笔為你收集整理的高级Web前端必会面试题知识点,大厂面试必备的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端面试JS三部分(三)
- 下一篇: html中visibility属性,(C