Vue原理(大廠必考)
- 組件化
- 響應式
- vdom和diff
- 模板編譯
- 渲染過程
- 前端路由
組件化基礎
- "很久以前"就有組件化
- 數據驅動視圖(MVVM,setState)
"很久以前"的組件化
- asp jsp php已經有組件化了
- nodejs中也有類似的組件化
數據驅動視圖
- 傳統組件,只是靜態渲染,更新還要依賴於操作DOM
- 數據驅動視圖——vue MVVM
- 數據驅動視圖——React setState(暫時按下不表)
Vue MVVM
總結
- 組件化
- 數據驅動視圖
- MVVM
Vue響應式
-
組件data的數據一旦變化,立刻觸發視圖的更新
-
實現數據驅動視圖的第一步
-
考察Vue原理的第一題
-
核心API——Object.defineProperty
-
如何實現響應式,代碼演示
-
Object.defineProperty的一些缺點(Vue3.0 啓用Proxy)
Proxy有兼容性問題
- Proxy兼容性不好,且無法polyfill
- Vue2.x還會存在一段時間,所以都得學
- Vue3.0相關知識,下一章講,這裏只是先提一下
Object.defineProperty基本用法
- 監聽對象,監聽數組
- 複雜對象,深度監聽
- 幾個缺點
深度監聽與數組監聽
//觸發更新視圖
function updateView(){
console.log('視圖更新')
}
//重新定義數組原型
const oldArrayProperty=Array.prototype
//創建新對象,原型指向oldArrayProperty,再擴展新的方法不會影響原型
const arrProto=Object.create(oldArrayProperty);
['push','pop','shift','unshift','splice'].forEach(methodName=>{
arrProto[methodName]=function(){
updateView()//觸發視圖更新
oldArrayProperty[methodName].call(this,...arguments)
}
})
//重新定義屬性,監聽起來
function defineReactive(target,key,value){
observer(value)
//核心 API
Object.defineProperty(target,key,{
get(){
return value;
},
set(newValue){
if(newValue!==value){
observer(newValue)
//設置新值
//注意,value一直在閉包中,此處設置完之後,再get時也是會獲取最新的值
value=newValue
//觸發更新視圖
updateView()
}
}
} )
}
//監聽對象屬性
function observer(target){
if(typeof target!=='object'||target===null){
//不是對象或數組
return target;
}
if(Array.isArray(target)){
target.__proto__=arrProto;
}
//重新定義各個屬性(for in 也可以遍歷數組)
for(let key in target){
defineReactive(target,key,target[key])
}
}
const data={
name:'zhangsan',
age:20,
info:{
address:'北京' //需要深度監聽
},
nums:[10,20,30]
}
//監聽數據
observer(data)
//測試
//data.name='lisi'
//data.age=21
//data.age={num:21};
//data.age.num=22;
//data.x='100' //新增屬性,監聽不到——所以有Vue.set
//delete data.name //刪除屬性,監聽不到——所以有Vue.delete
//data.info.address='上海' //深度監聽
data.nums.push(4) //監聽數組
Object.defineProperty()的缺點
- 深度監聽,需要遞歸到底,一次性計算量大
- 無法監聽新增屬性/刪除屬性
- 無法原生監聽數組,需要特殊處理
總結
- 基礎API——Object.defineProperty
- 如何監聽對象(深度監聽),監聽數組
- Object.defineProperty的缺點
虛擬DOM(Virtual DOM)和diff
-
vdom是實現vue和React的重要基石
-
diff算法是vdom中最核心,最關鍵的部分
-
vdom是一個熱門話題,也是面試中的熱門問題
-
DOM操作非常耗費性能
-
以前用jQuery,可以自行控制DOM操作的時機,手動調整
-
Vue和React是數據驅動視圖,如何有效控制DOM操作?
解決方案-vdom
- 有了一定複雜度,想減少計算次數比較難
- 能不能把計算,更多的轉移爲JS計算?因爲JS執行速度很快
- vdom-用JS模擬DOM結構,計算出更小的變更,操作DOM
用JS模擬DOM結構
通過snabbdom學習vdom
- 簡潔強大的vdom庫,易學易用
- Vue參考它實現的vdom和diff
- Vue3.0重寫了vdom的代碼,優化了性能
- 但vdom的基本理念不變,面試考點也不變
- React vdom具體實現和Vue也不同,但不妨礙統一學習
diff算法
- diff算法是vdom中最核心,最關鍵的部分
- diff算法能在日常使用vue React中體現出來(如key)
- diff算法是前端熱門話題,面試"寵兒"
- diff即對比,是一個廣泛的概念,如linux diff命令,git diff等
- 兩個js對象也可以做diff
- 兩棵樹做diff,如這裏的vdom diff
diff算法概述
樹diff的時間複雜度O(n^3)
- 第一,遍歷tree1;第二,遍歷tree2
- 第三,排序
- 1000個節點,要計算1億次,算法不可用
優化時間複雜度到O(n)
-
只比較同一層級,不跨級比較
-
tag不相同,則直接刪掉重建,不再深度比較
-
tag和key,兩者都相同,則認爲是相同節點,不再深度比較
diff算法總結 -
patchVnode
-
addVnodes removeVnodes
-
updateChildren(key的重要性)
vdom和diff-總結
- 細節不重要,updateChildren的過程也不重要,不要深究
- vdom核心概念很重要:h,vnode,patch,diff,key等
- vdom存在的價值更重要:數據驅動視圖,控制DOM操作
模板編譯
- 模板是vue開發中最常用的部分,即與使用相關聯的原理
- 它不是html,有指令,插值,JS表達式,到底是什麼?
- 面試不會直接問,但會通過“組件渲染和更新過程”考察
- 前置知識:JS的with語法
- vue template complier將模板編譯爲render函數
- 執行render函數生成vnode
with語法
- 改變{}內自由變量的查找規則,當做obj屬性來查找
- 如果找不到匹配的obj屬性,就會報錯
- with要慎用,它打破了作用域規則,易讀性變差
編譯模板
- 模板編譯爲render函數,執行render函數返回vnode
- 基於vnode在執行patch和diff
- 使用webpack vue-loader,會在開發環境下編譯模板(重要)
vue組件中使用render代替template
- 講完模板編譯,再講這個render,就比較好理解了
- 在有些複雜情況中,不能用template,可以考慮用render
- React一直都用render(沒有模板),和這裏一樣
總結
- with語法
- 模板到render函數,再到vnode,再到渲染和更新
- vue組件可以用render代替template
組件渲染/更新
- 一個組件渲染到頁面,修改data觸發更新(數據驅動視圖)
- 其背後原理是什麼,需要掌握哪些要點
- 考察對流程瞭解的全面程度
回顧學過的知識
- 響應式:監聽data屬性getter setter(包括數組)
- 模板編譯:模板到render函數,再到vnode
- vdom:patch(elem,vnode)和patch(vnode,newVnode)
- 初次渲染過程
- 更新過程
- 異步渲染
初次渲染過程
- 解析模板爲render函數(或在開發環境已完成,vue-loader)
- 觸發響應式,監聽data屬性getter setter
- 執行render函數,生成vnode,patch(elem,vnode)
-
修改data,觸發setter(此前在getter中已被監聽)
-
重新執行render函數,生成newVnode
-
patch(vnode,newVnode)
異步渲染 -
回顧$nextTick
-
彙總data修改,一次性更新視圖
-
減少DOM操作次數,提高性能
總結1
- 渲染和響應式的關係
- 渲染和模板編譯的關係
- 渲染和vdom的關係
總結2
- 初次渲染過程
- 更新過程
- 異步渲染
前端路由原理
-
稍微複雜一點的SPA,都需要路由
-
vue-router也是vue全家桶的標配之一
-
屬於“和日常使用相關聯的原理”,面試常考
-
回顧vue-router的路由模式
-
hash
-
H5 history
hash的特點 -
hash變化會觸發網頁跳轉,即瀏覽器的前進,後退
-
hash變化不會刷新頁面,SPA必須的特點
-
hash永遠不會提交到server端(前端自生自滅)
如何使用JS實現hash路由
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>hash test</title>
</head>
<body>
<p>hash test</p>
<button id="btn1">修改 hash</button>
<script>
//hash變化,包括:
//a.JS修改url
//b.手動修改url的hash
//c.瀏覽器前進,後退
window.onhashchange=(event)=>{
console.log('old url',event.oldURL)
console.log('new url',event.newURL)
console.log('hash',location.hash)
}
//頁面初次加載,獲取hash
document.addEventListener('DOMContentLoaded',()=>{
console.log('hash:',location.hash)
})
document.getElementById('btn1').addEventListener('click',()=>{
location.href='#/user';
})
</script>
</body>
</html>
H5 history
- 用url規範的路由,但跳轉時不刷新頁面
- history.pushState
- window.onpopstate
正常頁面瀏覽
改造成H5 history模式
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>history API test</title>
</head>
<body>
<p>history API test</p>
<button id="btn1">修改 url</button>
<script>
//頁面初次加載,獲取path
document.addEventListener('DOMContentLoaded',()=>{
console.log('load',location.pathname)
})
//打開一個新的路由
//【注意】用pushState方式,瀏覽器不會刷新頁面
document.getElementById('btn1').addEventListener('click',()=>{
const state={name:'page1'}
console.log('切換路由到','page1')
history.pushState(state,'','page1')
})
//監聽瀏覽器前進,後退
window.onpopstate=(event)=>{
console.log('onpopstate',event.state,location.pathname);
}
//需要server 端配合,可參考
//https://router.vuejs.org/zh/guide/essentials/history-mo
</script>
</body>
</html>
總結
- hash——window.onhashchange
- H5 history——history.pushState和window.onpopstate
- H5 history 需要後端支持
兩者選擇
- to B的系統推薦用hash,簡單易用,對url規範不敏感
- to C的系統,可以考慮選擇H5 history,但需要服務端支持
- 能選擇簡單的,就別用複雜的,要考慮成本和收益
Vue原理——總結
-
組件化
-
響應式
-
vdom和diff
-
模板編譯
-
渲染過程
-
前端路由
-
組件化的歷史
-
數據驅動視圖
-
MVVM
-
Object.defineProperty
-
監聽對象(深度),監聽數組
-
Object.defineProperty的缺點(Vue 3用Proxy,後面會講)
vdom和diff
- 應用背景
- vnode結構
- snabbdom使用:vnode h patch
模板編譯
- with語法
- 模板編譯爲render函數
- 執行render函數生成vnode
組件渲染/更新過程
- 初次渲染過程
- 更新過程
- 異步渲染
前端路由原理
-
hash
-
H5 history
-
兩者對比