對瀏覽器內核的理解?
- 由2部分組成,分別是渲染引擎和js引擎
- 渲染引擎:負責取得網頁內容整理訊息,以及計算網頁顯示方式
- js引擎:解析和執行js來實現網頁動態效果
h5新特性
- 新增了canvas video radio
- 本地離線存儲 localstorage
- sessionStorage瀏覽器關閉後自動刪除
- 新增一些語義化的標籤article、footer、header、nav、section
- 表單控件calendar date time email url search
- 新的技術webworker websocket Geolocation
- IE8/IE7/IE6支持通過document.createElement方法產生的標籤
- 可以利用這一特性讓這些瀏覽器支持HTML5新標籤
- 瀏覽器支持新標籤後,還需要添加標籤默認的樣式
webpack
打包效率:
- 開發環境採用增量構建,啓用熱更新
- 開發環境不做無意義的工作例如提取css計算文件hash等
- 配置devtool
- 選擇合適的loader
- 個別loader開啓cache如babel-loader
- 第三方庫採用引入方式
- 提取公共代碼
- 優化構建時搜索路徑 指明需要構建目錄及不需要構建目錄
- 模塊引入需要的部分
loader:
- loader就是一個node模塊,它輸出一個函數。當某種資源需要這個loader轉換時
這個函數會被調用。並且,這個函數可以通過提供給他的this上下文訪問Loader API
webpack的一些plugin(插件),怎麼使用webpack對項目進行優化
- 構建優化
-
減少編譯體積
ContextReplacementPugin,
IgnorePlugin
Babel-plugin-import
babel-plugin-transform-runtime -
並行編譯
happypack,
thread-loader,
uglifyjsWebpackPlugin
開啓並行 -
緩存
cache-loader,
hard-source-webpack-plugin,
uglifyjsWebpackPlugin開啓緩存,
babel-loader開啓緩存 -
預編譯
dllWebpackPlugin && DllReferencePlugin
auto-dll-webpack-plugin
- 性能優化
-
減少編譯體積
Tree-shaking,
Scope Hositing -
hash緩存
webpack-md5-plugin -
拆包
splitChunksPlugin
import()
require.ensure
webpack優化項目
- 對於webpack4.0,打包項目使用production模式,這樣會自動開啓代碼壓縮
- 使用ES6模塊開啓tree shaking,這個技術可以移除沒有使用的代碼
- 優化圖片,對於小圖可以使用base64的方式寫入文件中
- 按照路由拆分代碼,實現按需加載
優化打包速度
- 減少文件搜索範圍
- 比如通過別名(abc as rua)
- loader的test,include&exclude
- webpack默認壓縮並行
- happypack併發調用
- babel也可以緩存編譯
Babel原理
- 本質上就是編譯器:當代碼轉爲字符串AST,對AST進行轉變最後在生成新的代碼
- 分爲三步:此法分析生成token,語法分析生成AST,遍歷AST,根據插件變換相應的節點,最後吧AST轉換爲代碼
如何實現一個插件(封裝類,以及封裝類的調用)
- 通過插件apply函數傳入compiler對象
- 通過compiler對象監聽事件
apply (compiler) { const afterEmit = (compilation, cb) => { cb() setTimeout(function () { process.exit(0) }, 1000) } compiler.plugin('after-emit', afterEmit) } } module.exports = BuildEndPlugin
cdn
- 內容分發網絡,基本思路是儘可能避開互聯網上有可能影響數據傳輸速度的穩定性的瓶頸和環節,是內容傳輸的更快,更穩定
內存泄漏
-
定義:程序中已動態分配的堆內存由於某種原因未釋放或者無法釋放引發的各種問題
-
結果:變慢,崩潰,延遲大等
-
原因:
- 全局變量過渡使用
- dom情況時,還存在引用
- ie中使用閉包
- 定時器未清除
- 子元素存在引起的內存泄漏
-
避免策略
- 減少不必要的全局變量,或者什麼週期較長的對下那個,及時對無用的數據進行垃圾回收
- 注意程序邏輯,避免死循環之類的
- 避免創建過多對象原則:不用了的東西 要及時歸還
- 減少層級過多的引用
前後端路由差別
- 後端每次路由請求都是重新訪問服務器
- 前端路由實際上只是js根據url來操作dom元素,根據每個頁面需要的去服務端請求數據,返回數據後和模板進行組合
bind,call,apply的區別
- call和apply都是爲了解決改變this的指向,作用都是相同的,傳參方式有所差別call爲字符串參數apply爲數組參
- 第一個參數爲this所綁定的對象
簡單說下原型鏈
-
每個函數都有
prototype
屬性,除了
FUnction.prototype.bind()
,該屬性指向原型 -
每個對象都有_proto_
屬性,指向創建該對象的構造函數的原型,其實這個屬性指向了
[[prototype]]
但是
[[prototype]]
是內部屬性,我們並不能訪問到,所以使用
_proto_
來訪問 -
對象可以通過
_proto_
來尋找不屬於該對象的屬性,
_proto_
將對象連接起來組成了原型鏈
數組降維
- 普通數組降維
[1, [2], 3].flatMap(v => v)
// -> [1, 2, 3]
- 如果想將一個多維數組徹底的降維,實現方法
const flattenDeep = (arr) => Array.isArray(arr)
? arr.reduce( (a, b) => [...a, ...flattenDeep(b)] , [])
: [arr]
flattenDeep([1, [[2], [3, [4]], 5]])
事件代理
- 如果一個節點中的子節點是動態生成的,那麼子節點需要註冊事件的話應該註冊在父節點上
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
let ul = document.querySelector('#ul')
ul.addEventListener('click', (event) => {
console.log(event.target);
})
</script>
- 事件代理的方式相對於直接給目標註冊事件來說,有2個優點
- 節省內存
- 不需要給子節點註銷事件
性能優化
瀏覽器緩存
- 瀏覽器緩存對於前端性能優化來說是個很重要的點,良好的緩存策略可以降低資源的重複加載提高網頁整體的加載速度
主要分爲2種:強緩存和協商緩存
-
強緩存:實現強緩存可以通過2中響應頭實現,
Expires
和
Cache-Control
強緩存表示在緩存旗艦不需要請求,state code爲200Expires: Wed, 22 Oct 2018 08:41:00 GMT
Expires是HTTP/1.0的產物,表示資源會在Expires: Wed, 22 Oct 2018 08:41:00 GMT後過期,需要再次請求,並且Expires受限於本地時間,如果修改了本地時間,可能造成緩存失效。
Cache-control: max-age=30
Cache-Control出現於HTTP/1.1,優先級高於Expires。該屬性表示資源會在30s後過期,需要再次請求。
-
協商緩存:
- 如果緩存過期了,我們就可以使用協商緩存來解決問題。協商緩存需要請求,如果緩存有效返回304.
- 協商緩存需要客戶端和服務端共同實現,和強緩存一樣,也有2種實現方式
Last-Modified和If-Modified-Since
- Last-Modified表示本文件最後修改日期,If-Modified-Since會將Last-Modefied的值發給服務器,詢問服務器在改日期後資源十分有更新,有更新的話會將新的資源發送回來。
- 但是如果在本地打開緩存文件,就會造成Last-Modified被修改,所以在HTTP/1.1出現了ETag
ETag和If-None-Match的區別
- ETag類似於文件指紋,If-None-Match會將ETag發送給服務器,詢問該資源ETag十分變動,如果
選擇合適的緩存策略
- 對於大部分的場景都可以使用強緩存配合協商緩存解決,但是在一些特殊的地方可能需要選擇特殊的緩存策略
- 對於某些不需要緩存的資源,可以使用Cache-control:no-store,表示該資源不需要被緩存
- 對於頻繁變動的資源,可以使用Cache-Control:no-cache並配合ETag使用,表示該資源已經被緩存,但是每次都會發送請求詢問資源是否更新。
- 對於代碼文件來說,通常使用Cache-Control:max-age=31536000並配合策略緩存使用,然後對文件進行指紋處理,一旦文件名變動就會立刻下載新的文件
瀏覽器性能問題
-
重繪和迴流
- 重繪和迴流是渲染步驟中的一小節,但是這2個步驟對於性能影響很大
- 重繪是當節點需要更改外觀而不會影響佈局的,比如改變color就稱爲重繪
- 迴流是佈局或者幾何屬性需要改變就稱爲迴流。
- 迴流必定會發生重繪,重繪不一定發生會迴流,迴流蘇so需要的成本比重繪高得多,改變深層次的節點很可能導致父節點的一系列迴流。
-
可能導致性能問題的幾個動作
- 改變widnow的大小
- 改變字體
- 添加或者刪除樣式
- 文字改變
- 定位或者浮動
- 盒模型
-
重繪和迴流和Event loop的關係
- 當Event loop執行完Microtasks後,會判斷document是否需要更新。因爲瀏覽器是60hz刷新率,每16ms纔會更新一次
- 然後判斷是否有resize或者scroll,有的話會去觸發事件,所以resize和scroll事件也是至少16ms纔會觸發一次,並且自帶節流功能
- 判斷是否觸發了media query
- 更新動畫並且發送事件
- 判斷是否有全屏操作事件
- 執行requestAnimationFrame回調
- 執行IntersectionObserver回調,該方法用於判斷元素是否可見,可以用於懶加載上,但是兼容性不太好
- 更新界面
- 以上是一幀中可能會做的事情,如果在一幀中有空閒時間,就回去執行requestIdleCallback回調
-
減少重繪和迴流
- 用translate代替top
- 使用visibility替換display:none,前者只會引起重繪,後者會引發迴流(改變了佈局)
- 不要把DOM節點的屬性值放到一個循環裏當成循環的變量
for(let i = 0; i < 1000; i++) { // 獲取 offsetTop 會導致迴流,因爲需要去獲取正確的值 console.log(document.querySelector('.test').style.offsetTop) }
- 不要使用table佈局,可能很小的改動會造成整個table的重新佈局 動畫實現的速度的選擇,動畫速度越快,迴流次數越多,也可以選擇使用requestAnimationFrame
- css選擇符從右往做匹配查找,避免DOM深度過深
- 將頻繁運行的動畫變爲圖層,圖層能夠阻止改節點回流影響別的元素。比如video標籤,瀏覽器會自動將改節點變爲圖層
使用HTTP/2.0
- 因爲瀏覽器會有併發請求限制,在HTTP/1.1時代,每個請求都需要建立和斷開,消耗了好幾個RTT時間,並且由於TCP慢啓動的原因,加載體積大的文件會需要更多的時間
- 在HTTP/2.0中引入了多路複用,能夠讓多個請求使用同一個TCP鏈接,極大的加快了網頁的加載速度。並且還支持Header壓縮,進一步的減少了請求的數據大小
預加載
- 在開發中,可能會遇到這樣的情況,有些資源不需要馬上用到,但是希望儘早獲取,這個時候就可以使用預加載
- 與價值啊其實是聲明式的fetch,強制瀏覽器請求資源,並且不會阻塞onload事件,可以使用以下代碼開啓預加載
預加載可以一定程度上降低首屏的加載時間,因爲可以將一些不影響首屏但重要的額文件延後加載,缺點是兼容性不太好<link rel="preload" href="http://example.com">
預渲染
- 可以通過預渲染將下載的文件預先在後臺渲染,可以使用以下代碼開啓預渲染
<link rel="prerender" href="http://poetries.com">
- 預渲染雖然可以提高頁面的加載速度,但是要確保該頁面百分百會被用戶在之後打開,否則就白白浪費資源去渲染
懶執行
- 懶執行就是將某些邏輯延遲到使用時計算。該技術可以用於首屏優化,對於某些耗時邏輯並不需要在首屏就使用的,就可以使用懶執行。懶執行需要喚醒,一般可以通過定時器或者事件的調用來喚醒
懶加載
- 懶加載就是將不關鍵的資源延後加載
- 懶加載的原理就是隻加載自定義區域(通常是可視區域,但也可以是即將進入可視區域)
內需要加載的東西。對於圖片來說,先設置圖片標籤的src屬性爲一張佔位圖,將真實的圖片資源放在一個自定義屬性中,當進入自定義區域時,就將自定義屬性替換爲src屬性,這樣圖片就會去下載資源實現懶加載
- 懶加載的原理就是隻加載自定義區域(通常是可視區域,但也可以是即將進入可視區域)
- 懶加載不僅可以用於圖片,也可以使用在別的資源上。比如進入可視區纔開始播放的視頻等。
文件優化
- 圖片優化
- 如何優化圖片,有2個思路
- 減少像素點
- 減少每個像素點能夠顯示的顏色
- 如何優化圖片,有2個思路
- 圖片加載優化
- 不用圖片。很多時候會使用到很多修飾類圖片,其實這類修飾類圖片完全可以用css去代替。
- 對於移動端來說,屏幕寬度就那麼點,完全沒必要去加載原圖浪費帶寬,一般圖片都用cdn加載,可以計算出適配屏幕的款都,然後去請求相應裁剪好的圖片
- 小圖使用base64格式
- 將多個圖標正和到一張圖片中(雪碧圖)
- 選擇正確的圖片格式:
- 對於更夠顯示WebP格式的瀏覽器儘量使用WebP格式。因爲WebP格式具有更好的圖片數據壓縮算法,能帶來更小的圖片體積,而且擁有肉眼識別無差異的圖像質量,缺點是兼容性不太好
- 小圖使用PNG,其實對於大部分圖標這類圖片,完全可以使用SVG代替
- 照片使用JPEG
- 其他文件優化
- css文件放在head中
- 服務端開啓文件壓縮功能(gzip)
- 將script標籤放在body底部,因爲js文件會阻塞渲染。當然吧script標籤放在任意位置然後加上defer,表示該文件會並行下載,但是會放到HTML解析完成後順序執行,對於沒有任何依賴的js文件可以加上async,表示加載和渲染後續文檔元素的過程將和js文件的加載與執行並行無序進行。執行js代碼過長會卡住渲染,對於需要很多時間計算的代碼可以考慮用Webworker,Webworker可以讓我們另開一個線程執行腳本而不影響渲染
- cdn
- 靜態資源儘量使用cdn加載,由於瀏覽器對於單個域名有併發請求上線,可以考慮使用多個cdn域名。對於cdn加載靜態資源需要注意域名要與主站的不同,否則每次請求都會帶上主站的cookie
安全
xss
- 跨網站指令碼(Cross-site-scripting簡稱xss)是一種網站應用程式的安全漏洞攻擊,是代碼注入的一種。他允許惡意使用者將程式碼注入到網頁上,其他使用者在觀看網頁時就會受到影響。這類攻擊通常包含html以及使用者端腳本語言
- xss分爲三種:反射型,儲存型和DOM-based
- 如何攻擊
- xss通過修改html節點或者執行js代碼來攻擊網站
- 例如通過url獲取某些參數
<!-- http://www.domain.com?name=<script>alert(1)</script> --> <div>{{name}}</div>
- 如何防禦
- 最普遍的做法是轉義輸入輸出的內容,對於引號,尖括號,斜槓進行轉義
function escape(str) { str = str.replace(/&/g, "&"); str = str.replace(/</g, "<"); str = str.replace(/>/g, ">"); str = str.replace(/"/g, "&quto;"); str = str.replace(/'/g, "&##39;"); str = str.replace(/`/g, "&##96;"); str = str.replace(/\//g, "&##x2F;"); return str }
- 最普遍的做法是轉義輸入輸出的內容,對於引號,尖括號,斜槓進行轉義
csrf
- 跨站請求僞造,也被稱爲one-click attack或者session riding通縮寫爲csrf或者xsrf,是一種挾制用戶在當前已登錄的web應用程序上執行非本意的攻擊方法
- csrf就是利用用戶的登錄態發起惡意請求
- 如何攻擊
- 假設網站中有一個通過get請求提交用戶評論的接口,那麼攻擊者就可以在釣魚網站中加入一個圖片,圖片的地址就是評論接口
<img src="http://www.domain.com/xxx?comment='attack'"/>
- 假設網站中有一個通過get請求提交用戶評論的接口,那麼攻擊者就可以在釣魚網站中加入一個圖片,圖片的地址就是評論接口
- 如何防禦
- get請求不對數據進行修改
- 不讓第三方網站訪問到用戶cookie
- 阻止第三方網站請求接口
- 請求時附帶驗證信息,比如驗證碼或者token
密碼安全
- 加密的密碼添加自定義字段後進行二次加密
ES6中的Set和Map
ES6中的Set
- ES6中提供Set數據容器,這是一個能夠 存儲無重複值 的有序列表。
- 創建Set
- 通過new Set()可以創建Set,然後通過add方法能夠向Set中添加數據項:
let cc = new Set() cc.add(1) cc.add("1") console.log(cc)//{1,"1"} console.log(cc.size);//2
- Set內部使用Object.is()方法來判斷兩個數據項是否相等,唯一不同的是+0和-0在Set中被判斷爲是相等的。
- 同時可以使用數組來構造Set,或者說具有迭代器的對象都可以用來構造Set,並且Set構造器會確保不會存在重複的數據項:
let dd = new Set([1,2,2,3,3,3,5]); console.log(dd)//{1,2,3,5} console.log(dd.size)//4
- 可以用has()方法判斷某個值是否存在於Set中:
let ccc = new Set([1,2,3,4,4]) console.log(ccc.has(4))//true console.log(ccc.has(5))//false
- 使用delete()方法從Set中刪除某個值,或者使用clear()方法從Set中刪除所有值:
let set = new Set([1,2,3,3,3,3]) console.log(set.size)//3 console.log(set.has(5))//false set.delete(1) console.log(set.has(1))//false console.log(set.size)//2
- 可以使用forEach方法來遍歷Set中的數據項,該方法傳入一個回調函數callback,還可以傳入一個this(如果是箭頭函數可以省略入參),用於回調函數中:
let set = new Set([1,2,3,3,3,3]) let operation = { print(value){ console.log(value) }, iterate(set=[]){ set.forEach((value,key)=>{ this.print(value) this.print(key) }) } } operation.iterate(set)
- 將Set轉化成數組十分容易,可以將數組傳入Set構造器即可;而將Set轉換成數組,需要使用擴展運算符。擴展運算符能將數組中的數據項切分開,作爲獨立項傳入到函數,如果將擴展運算符用於可迭代對象的話,就可以將可迭代對象轉換成數組:
let ccc = new Set([1,2,3,1,5]) let [...arr] = ccc console.log(ccc) console.log(arr)
- Set在存放對象時,實際上是存放對象的引用,即Set也被稱爲Strong Set。如果所儲存的對象被置爲null,但是Set實例仍然存在的話,對象依然無法被垃圾回收器回收,從而無法釋放內存:
可以看出就算對象key置爲null,但是由於是強引用的方式,Set實例還存在,對象key依然不會被回收。let ccc = new Set(); let key = {} let key2 = {} ccc.add(key) ccc.add(key2) console.log(ccc.size)//2 key = null console.log(ccc.size)//2
- 如果想讓對象key正常釋放的話,可以使用WeakSet,此時存放的是對象的弱引用,當對象只被Set弱引用的話,並不會阻止對象實例被回收。WeakSet同Set的用法機會一直。可以使用add()方法增加數據項,使用has()方法檢查WeakSet中是否包含某項,以及使用delete方法刪除某一項。
let ccc = new WeakSet(); let key = {} set.add(key) console.log(set.has(key))//true set.delete(key); console.log(set.has(key))//false
- 但是需要注意的是:WeakSet構造器不接受基本類型數據,只接受對象。同樣的可以使用可迭代的對象如數組,來作爲構造器參數沒來創建WeakSet
- 對於WeakSet和Set之間的重要差異:
- 對於WeakSet實例,若調用add()方法時傳入了非對象的參數,則會拋出錯誤。如果是has()或者delete()方法中傳入了非對象的參數則會返回false;
- WeakSet不可迭代,因此不能用於for-of循環
- WeakSet無法暴露出任何迭代器(例如keys()與values()方法),因此沒有任何編程手段可以用於判斷WeakSet的內容。
- WeakSet沒有forEach()方法
- WeakSet沒有size屬性
- 通過new Set()可以創建Set,然後通過add方法能夠向Set中添加數據項:
- 創建Set
ES6中的Map
- ES6中提供了Map數據結構,能夠存放鍵值對,其中,鍵的去重是通過Object.is()方法進行比較,鍵的數據類型可以是基本類型數據也可以是對象,而值也可以是任意類型數據。
- 使用set()方法可以給Map添加鍵值對
通過set()方法往Map中增加了2個鍵值對後,可以看到Map的大小就爲2let ccc = new Map(); ccc.set("rua","6666") ccc.set("year","2020") console.log(map)//[{key:"rua",value:"6666"},{key:"year",value:"2020"}] console.log(map.size)//2
2. 通過get()方法可以從Map中提取值let ccc = new Map(); ccc.set("rua","6666") ccc.set("year","2020") console.log(cc.get("rua"))//"6666"
- has(),delete()以及clear()方法
- 爲了和Set的操作保持一致,Map中同樣有has()方法,用來檢查某個數據項是否存在於Map中,使用delete方法可以從Map中刪除一個數據項,使用clear()方法可以刪除Map所有的數據項
與Set的初始化一樣,Map也可以用數組來初始化Map,該數組中的每一個數據項也是數組,數組的第一個數據項代表建值對的鍵,第二個數據項是鍵值對的值:let ccc = new Map(); ccc.set("rua","6666") ccc.set("year","2020") console.log(ccc.has("rua"))//true ccc.delete("rua") console.log(ccc.has("rua"))//false ccc.clear() console.log(ccc.size)
//使用數組來創建Map let ccc = new Map([["rua","6666"],["year","2020"]]) console.log(ccc.has(rua))//true console.log(ccc.has(year))//true console.log(ccc.size)//2
- WeakMap和WeakSet類似
-
ES6中Set和Map的總結
- Set是無重複值的有序列表。根據Object.is()方法來判斷其中的值不相等,以保證無重複。Set會自動移除重複的值,因此你可以使用它來過濾數組中的重複值並返回結果,Set並不是數組的子類型,所以你無法隨機訪問其中的值,但你可以使用has()方法來判斷某個值是否存在於Set中 ,或者通過size屬性來查看其中有多少個值。Set類型還擁有forEach()方法,用於處理每個值。
- WeakSet是隻能包含對象的特殊Set。其中的對象使用弱引用來存儲,意味着當WeakSet中的項是某個對象的僅存引用時,他不會屏蔽垃圾回收。由於內存管理的複雜性,WeakSet的內容不能被檢查,因此最好將WeakSet僅用於追蹤需要被歸組在一起的對象。
- Map是有序的鍵值對,其中的鍵允許是任何類型。與Set相似,通過調用Object.is()方法來判斷重複的鍵,這意味着能將數值5與字符串"5"作爲2個相對獨立的鍵,使用Set()方法能將任何類型的值關聯到某個鍵上,並且該值此後能用get()方法提取出來。Map也擁有一個size屬性與一個forEach()方法,讓項目訪問更容易。
- WeakMap是隻能包含對象類型的鍵的特殊Map。與WeakSet相似,鍵的對象引用是弱引用,因此當他是某個對象的僅存引用時,也不會屏蔽垃圾回收。當鍵被回收後,所關聯的值也同時從WeakMap中移除。
React
- React中的keys的作用是什麼?
- Keys是React用於追蹤那些列表元素被修改,被添加或者被移除的輔助標識
- 在開發過程中,我們需要保證某個元素的key在其統計元素中具有唯一性。在React Diff算法中React會藉助元素的key值來判斷該元素是新近創建的還是被移動而來的元素,從而減少不必要的元素重渲染。此外,React還需要藉助key值來判斷元素與本地狀態的關聯。
- 在生命週期中的哪一步你應該發起ajax請求?
- 我們將ajax請求放在componentDidMount函數中執行,主要原因:
- React下一代調和算法Fiber會通過開始或停止渲染的方式優化應用性能,其會影響到componentWillMount的觸發算法。對於ComponentWillMount這個什麼周期函數的調用次數會變得不確定,React可能會多次頻繁調用componentWillMount。如果我們將ajax方法放到componentWillMount函數中,那麼顯而易見其會被觸發多次,自然不是最好的選擇。
- 如果我們將ajax請求放置到什麼週期的其他函數中,我們並不能保證請求在組件掛載完畢後纔會要求響應。如果我們的數據請求在組件掛載之前完成,並且調用了SetState函數將數據添加到組件狀態中,對於未掛載的組件則會報錯。而在componentDidMount函數中進行ajax請求則能有效避免這個問題
- shouldComponentUpdate的作用
- shouldComponentUpdate允許我們手動的判斷是否要進行組件更新,根據組件的應用場景設置函數的合理返回值能夠幫助我們避免不必要的更新
- 概述下React中的事件處理邏輯
- 爲了解決跨瀏覽器兼容性問題,React會將瀏覽器原生事件(Browser Native Event)封裝爲合成事件(SyntheticEvent)傳入設置的事件處理器中。這裏的合成事件提供了與原生事件相同的接口,不過他們屏蔽了底層瀏覽器的細節差異,保證了行爲的一致性。另外有意思的是,React並沒有直接將事件附着到子元素上,而是以單一事件監聽器的方式將所有的事件發送到頂層進行處理。這樣React在更新DOM的時候就不需要考慮如何去處理附着在DOM上的事件監聽,最終達到優化性能的目的
- redux有什麼缺點
- 一個組件所需要的數據,必須由父組件傳過來,而不能像flux直接從store取
- 當一個組件相關數據更新時,即使父組件不需要用到這個組件,父組件還是會重新render,可能會有效率影響,或者需要寫複雜的shouldComponentUpdate進行判斷
- react生命週期函數
- 初始化階段
- getDefaultProps:獲取實例的默認屬性
- getInitialState:獲取每個實例的初始化狀態
- componentWillMount:組件即將被裝載、渲染到頁面上
- render:組件在這裏生成虛擬的DOM節點
- omponentDidMount:組件真正在被裝載之後
- 運行中狀態
- componentWillReceiveProps:組件將要接收到屬性的時候調用
- shouldComponentUpdate:組件接受到新屬性或者新狀態的時候(可以返回false,接收數據後不更新,阻止render調用,後面的函數不會被繼續執行了)
- componentWillUpdate:組件即將更新不能修改屬性和狀態
- render:組件重新描繪
- componentDidUpdate:組件已經更新
- 銷燬階段
- componentWillUnmount:組件即將銷燬
- react性能優化是哪個周期函數
- shouldComponentUpdate這個方法用來判斷是否需要調用render方法重新描繪dom。因爲dom的描繪非常消耗性能,如果我們能在shouldComponentUpdate方法中能夠寫出更優化的dom diff算法,可以極大的提高性能
- 爲什麼虛擬dom會提高性能
- 虛擬dom相當於在js和真實dom中加了一個緩存,利用dom diff算法避免了沒有必要的dom操作,從而提高性能
- 具體實現步驟
- 用js對象結構表示dom樹的結構;然後用這個樹去構建一個真正的DOM樹,插到文檔當中
- 當狀態變更的時候,重新構造一顆新的對象樹。然後用新的樹和舊的樹進行比較,記錄2棵樹的差異
- 把2所記錄的差異應用到步驟1所構建的DOM樹上,視圖就更新了
- diff算法
- 把樹形結構按照層級分解,只比較同級元素
- 給列表結構的每個單元添加唯一的key屬性,方便比較。
- React只會匹配相同class的component(這裏面的class指的是組件的名字)
- 合併操作,調用component的setState方法的時候,React將其標記爲dirty,到每一個事件循環結束,React檢查所有標記dirty的component重新繪製
- 選擇性子樹渲染。開發人員可以重寫shouldComponentUpdate提高diff性能
- react性能優化方案
- 重寫shouldComponentUpdate來避免不必要的DOM操作
- 使用production版本的react.js
- 使用key來幫助react識別列表中所有子組件的最小變化
- react的虛擬dom是怎麼實現的
- 首先說說爲什麼要是用Virturl DOM,因爲操作真實DOM的耗費的性能代價太高,所以react內部使用js實現了一套dom結構,在每次操作真實dom之前,使用實現好的diff算法,對虛擬dom進行比較,遞歸找出有變化的dom節點,然後對其進行更新操作。爲了實現虛擬dom,我們需要把每一種節點類型抽象成對象,每一種節點類型有自己的屬性,也就是prop,每次進行diff的時候,react回顯比較該節點類型,假如節點類型不一樣,那麼react會直接刪除該節點,然後直接創建新的節點插入到其中,假如節點類型一樣,那麼會比較prop是否有更新,假如prop不一樣,那麼react會判定該節點有更新,那麼重渲染該節點,然後在對其子節點進行比較,一層一層往下,知道沒有子節點
- react的渲染過程中,兄弟節點之間是怎麼處理的?也就是key值不一樣的時候
- 通常我們輸出節點的時候都是map一個數組然後返回ReactNode,爲了方便內部進行優化,我們必須給每一個reactNode添加key,這個key prop在設計值處不是給開發者用的,而是給react用的,大概的作用就是給每一個reactNode添加一個身份表示,方便react進行識別,在重新渲染過程中,如果key一樣,若組件屬性有所變化,則react只更新組件對應點額屬性;沒有變化則不更新,如果key不一樣,則react先銷燬該組件然後重新創建該組件
VUE
1.詳細說下你對vue生命週期的理解
- 總共分爲8個階段創建前/後,載入前/後,更新前/後,銷燬前/後
- 創建前/後:在beforeCreate階段,vue實例的掛載元素el和數據對象data都是undefined還未初始化。在created節點,vue實例的數據對象data有了el還沒有
- 載入前/後:在beforeMount階段,vue實例的¥el和data都初始化了,但還是在掛載之前的虛擬dom節點,data.message還未替換。在mounted階段,vue實例掛載完成,data.message成功渲染。
- 更新前/後:當data變化時,會觸發beforeUpdate和update方法
- 銷燬前/後:在執行destroy方法後,對data的改變不會觸發觸發周期函數,說明此時vue實例已經解除了事件監聽以及和dom的綁定,但是dom結構依然存在
- vue路由的鉤子函數
- 首頁可以控制導航跳轉,beforeEach,afterEach等,一般用於頁面title的修改。一些需要登錄才能調整的頁面的重定向功能。
- beforeEach主要有三個參數to,form,next
- to:route即將進入的目標路由對象
- form:route當前當行正要離開的路由
- next:function一定要調用該方法resolve這個鉤子。執行效果依賴next方法的調用參數。可以控制網頁的跳轉
- router的區別
- $route是路由信息對象,包括path,params,hash,query,fullPath,matched,name等路由信息參數。
- 而$router是路由實例對象包括了路由的跳轉方法,鉤子函數
- keep-alive的作用是什麼
- keep-alive包裹動態組件時,會緩存不活動的組件實例,主要用於保留組件狀態或避免重新渲染。
- 比如有一個列表和一個詳情,那麼用戶就會經常執行打開詳情=>返回列表=>打開詳情。。這樣的話列表和詳情都是一個頻率很高的頁面,那麼就可以對列表組件使用keep-alive進行緩存,這樣用戶每次返回列表的時候,都能從緩存中快速渲染,而不是重新渲染
- NextTick
- nexttick可以讓我們在下次DOM更新循環結束之後執行延遲迴調。用於獲得更新後的DOM
- vue的優點是什麼?
- 低耦合。視圖(view)可以獨立於Model變化和修改,一個ViewModel可以綁定到不同的“view”上,當view變化的時候Model可以不變,當Model變化的時候View也可以不變
- 可重用性。你可以把一些視圖邏輯放在一個ViewModel裏面,讓很多view重用這段視圖邏輯
- 可測試。界面素來是比較難於測試的,而現在測試可以針對ViewModel來寫
- vue組件的data爲什麼必須是函數
- 每個組件都是vue實例
- 組件共享data屬性,當data的值是同一個引用類型的值時,改變其中一個會影響其他的
- mvvm
- 在mvvm中,ui是通過數據驅動的,數據一旦改變就會相應的刷新對應的ui,ui如果改變,也會改變對應的數據。這種方式就可惡意在業務處理中只關心數據的流轉,而無需直接和頁面打交道。viewmodel只關心數據和業務的處理,不關心view如何處理數據,在這種情況下,view和model都可以獨立出來,任何一方改變了也不一定需要改變另一方,並且可以將一些可服用的邏輯放在一個viewmodel中,讓多個view複用這個viewmodel
- 在mvvm中最核心的也就是數據雙向綁定,例如angular和react的髒數據檢測,vue中的數據劫持
- 髒數據檢測
- 當觸發了指定的事件後會進入髒數據檢測,這時會調用watch函數u,然後再次調用$digest循環直到沒有發現變化。循環至少爲2次 至多爲10次
- 髒數據檢測雖然存在低效問題,但是不關心數據是通過什麼方式改變的,都可以完成任務,但是這在vue中的雙向綁定是存在問題的。並且髒數據檢測可以實現批量檢測出更新的值,再去統一更新ui,大大減少操作DOM的次數,所以低效是相對的。
- 數據劫持
- vue內部使用了Object。definePropperty()來實現雙向綁定,通過這個函數可以監聽到set和get的事件
let data = { name : "fsh"} observer(data) let name = data.name data.name = "rua" function observer(obj){ //判斷類型 if( !obj || typeof obj !== "object" ){ return } Object.keys(obj).forEach(key=>{ defineReactive(obj,key,obj[key]) }) } function defineReactive(obj,key,val){ //遞歸子屬性 observer(val) Object.defineProperty(obj,key,{ enumerable:true, configurable:true, get:function reactiveGetter(){ console.log("get value") return val }, set:function reactiveSetter(newVal){ console.log("change value") val = newVal } }) }
- proxy與Object.defineProperty對比
- Object.defineProperty雖然已經能夠實現雙向綁定了,但是他還是有缺陷的
- 只能對屬性進行數據劫持,所以需要深度遍歷整個對象對於數組不能監聽到數據的變化
- 雖然vue中確實能檢測到數組數據的變化,但是其實是使用的hack方法,並且也是有缺陷的
const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) // hack 以下幾個函數 const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] methodsToPatch.forEach(function (method) { // 獲得原生函數 const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { // 調用原生函數 const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // 觸發更新 ob.dep.notify() return result }) })
- 反觀proxy就沒有以上的問題,原生支持數組變化,並且可以直接整個對象進行攔截,所以vue也將在下個大版本使用proxy替換Object.defineProperty
let onWatch = (obj, setBind, getLogger) => { let handler = { get(target, property, receiver) { getLogger(target, property) return Reflect.get(target, property, receiver); }, set(target, property, value, receiver) { setBind(value); return Reflect.set(target, property, value); } }; return new Proxy(obj, handler); }; let obj = { a: 1 } let value let p = onWatch(obj, (v) => { value = v }, (target, property) => { console.log(`Get '${property}' = ${target[property]}`); }) p.a = 2 // bind `value` to `2` p.a // -> Get 'a' = 2
- vue內部使用了Object。definePropperty()來實現雙向綁定,通過這個函數可以監聽到set和get的事件
- 髒數據檢測
- 爲什麼需要Virtual Dom?
- 衆所周知,操作dom是很耗費性能的一件事情,既然如此,我們可以考慮通過js對象來模擬dom對象,畢竟操作js對象比操作dom省時的多
- Virtual Dom算法簡述
- 既然我們已經通過js來模擬實現DOM,那麼接下來的難點就在於如何判斷舊的對象和新的對象之間的差異
- DOM是多叉樹的結構,如果需要完整的比較2顆樹的差異,那麼需要的時間複雜度會是O(n^3),這個複雜度肯定是不能夠接受的,於是React團隊優化了算法,實現了O(n)的複雜度來對比差異
- 實現O()複雜度的關鍵就是隻對比同層的節點,而不是跨層對比,這也是考慮到在實際業務中很少會去跨層的移動DOM元素
- 所以判斷差異的算法就分爲了2步
- 首先從上至下,從左往右遍歷對象,也就是樹的深度遍歷,這一步中會給每個節點添加索引,便於最後的渲染差異
- 一旦節點有子元素,就去判斷子元素是否有不同