Vue原理
未經(jīng)允許 禁止轉(zhuǎn)載
MVVM 數(shù)據(jù)驅(qū)動視圖
傳統(tǒng)組件只是靜態(tài)渲染,更新還要依賴于操作DOM
vue MVVM听绳,數(shù)據(jù)驅(qū)動視圖
react setState醒叁,數(shù)據(jù)驅(qū)動視圖
MVVM:Modev-View-ViewModel
1. Model:可以理解為vue里的data對象
2. View:頁面臂寝,DOM
3. ViewModel:Vue層送淆,處理一些DOM監(jiān)聽税产、指令操作等
MVVM就是通過vue監(jiān)聽怕轿、指令操作等方法修改data進(jìn)而渲染頁面
Vue響應(yīng)式
vue3.0之前
核心API:Object.defineProperty
Object.defineProperty基本用法:
var data = {}
var name = 'zhangsan'
Object.defineProperty(data,'name',{
get:function(){
console.log('get')
return name
},
set:function(newVal){
console.log('set')
name = newVal
}
})
console.log(data.name) //get zhangsan
data.name = 'liu' //set
console.log(data.name) //get liu
功能稍完整的Object.defineProperty:
//定義data
const data = {
name: 'zhangsan',
age: 14,
city:{ //需要深度監(jiān)聽
id: 010,
name: '北京'
}
}
//監(jiān)聽函數(shù)
function observer(target) {
//判斷傳入對象參數(shù)是否為object
if (typeof target !== 'object' || target == null) {
return target
}
//對傳入對象進(jìn)行for in遍歷
for (let key in target) {
//target--對象;key--對象屬性;target[key]--屬性值
defineProperty(target, key, target[key])
}
}
//obj.defineProperty
function defineProperty(target, key, value) {
//深度監(jiān)聽
observer(value)
Object.defineProperty(target, key, {
get() {
return value
},
set(newVal) {
if (newVal !== value) {
//深度監(jiān)聽
observer(value)
value = newVal
update()
}
}
})
}
//update
function update() {
console.log('update')
}
observer(data)
data.name = 'liu'
data.age = 15
Object.defineProperty的缺點:
1. 深度監(jiān)聽需要遞歸到底偷崩,一次性計算量大。
2. 無法監(jiān)聽新增/刪除屬性撞羽。(需要用Vue.set/Vue.delete)
虛擬DOM和diff算法
1. vdom是實現(xiàn)vue和react的重要基石
2. diff算法是vdom中最核心阐斜、最關(guān)鍵的部分
用JS模擬DOM結(jié)構(gòu)
<!-- DOM -->
<div class='container' id='div1'>
<p>vdom</p>
<ul style='font-size: 14px;'>
<li>a</li>
</ul>
</div>
//用JS模擬DOM
{
tag: 'div',
props:{
id: 'div1',
className: 'container'
},
children: [
{
tag: 'p',
children: 'vdom'
},
{
tag: 'ul',
props: {
style: 'font-size: 14px'
},
children: [
{
tag: 'li',
children: 'a'
}
]
}
]
}
vue、react的vdom參考了snabbdom
//snabbdom:
var container = document.getElementById('container')
var vnode = h(...) snabbdom通過h方法模擬出DOM結(jié)構(gòu)賦值給vnode
patch(container,vnode) //把vnode結(jié)構(gòu)賦值給container
snabbdom重點:1诀紊、h函數(shù)谒出;2、vnode數(shù)據(jù)結(jié)構(gòu)邻奠;3笤喳、patch函數(shù)
vdom總結(jié):
1. 用JS模擬DOM結(jié)構(gòu)(vnode)
2. 新舊vnode進(jìn)行對比,得出最小的更新范圍碌宴,最后更新DOM
3. 數(shù)據(jù)驅(qū)動視圖的模式下杀狡,有效控制DOM操作
diff算法
樹diff的時間復(fù)雜度O(n的3次方)
- 遍歷tree1
- 遍歷tree2
- 排序
1000個節(jié)點要計算1億次
優(yōu)化時間復(fù)雜度到O(n)
- 只比較同一層級,不跨級比較
- tag不相同贰镣,則直接刪除重建呜象,不再深度比較
- tag和key膳凝,兩者都相同,則認(rèn)為是相同節(jié)點恭陡,不再深度比較
1000個節(jié)點只需計算1000次
snabbdom源碼解讀
1. h函數(shù)
h函數(shù)一般接受3個參數(shù):sel(元素標(biāo)簽)蹬音、data(元素屬性)、children(子內(nèi)容)休玩。也可單獨接受其中一兩個參數(shù)著淆。
h函數(shù)返回執(zhí)行一個vnode函數(shù),參數(shù)包括sel,data,children,text(如果children為字符串拴疤,則用text顯示該字符串),undefined
2. vnode函數(shù)
返回一個對象牧抽,包含sel、data遥赚、children扬舒、text、elm(該DOM節(jié)點)凫佛、key
3. patch函數(shù)
- 接受的第一個參數(shù)為element||vnode讲坎,第二個參數(shù)為vnode。
- 執(zhí)行pre hook生命周期愧薛。
- 判斷第一個參數(shù)是否為vnode晨炕,不是的話(傳入的第一個參數(shù)為一個DOM)則創(chuàng)建一個空vnode關(guān)聯(lián)到這個DOM元素。
4. sameVnode函數(shù)
執(zhí)行sameVnode判斷兩個vnode的key和ele是否都相等毫炉,相等的話則執(zhí)行patchVnode函數(shù)瓮栗,不相同則刪除銷毀舊的vnode,然后用新的vnode重建瞄勾。
5. patchVnode函數(shù)
- 執(zhí)行prepatch hook生命周期鉤子费奸。
- 設(shè)置新vnode的ele,把舊vnode的ele賦給新的进陡。
- 判斷新舊children
6. addVnodes函數(shù)
有舊的children愿阐,沒有新的,則添加vnode
7. removeVnodes函數(shù)
沒有舊的children趾疚,有新的缨历,則移除vnode
8. updateVnode函數(shù)
對比children,如開始和開始作對比糙麦,如果相同辛孵,則執(zhí)行patchVnode函數(shù),執(zhí)行后index會進(jìn)行累加或者累減赡磅,直到對比完成魄缚。
如果幾種對比方式(start-start,end-end,start-end,end-start)都未命中,則用key和sel進(jìn)行對比仆邓,如果都相等則執(zhí)行patchVnode函數(shù)鲜滩。
從這里可以看出v-for使用key的重要性伴鳖,不使用key的話無法做對比,直接銷毀舊的創(chuàng)建新的徙硅,另外key如果是隨機(jī)數(shù)或者index則也無法對比榜聂。所以key是有必要寫的且不能亂寫
diff算法總結(jié)
1. patchVnode
2. addVnodes removeVnodes
3. updateChildren(key的重要性,可回答v-for為什么要有key:因為vdom的diff算法會對比select節(jié)點和key是否相同嗓蘑,相同則繼續(xù)深入對比须肆,不相同則重建,所以key是必要的)
vdom和diff算法總結(jié)
vdom核心概念很重要:h桩皿、vnode豌汇、patch、diff泄隔、key等拒贱。
vdom存在價值更重要:數(shù)據(jù)驅(qū)動視圖,控制DOM操作佛嬉。
模板編譯
with語法
const obj = {
a: 1,
b: 2
}
console.log(obg.a)
console.log(obg.b)
console.log(obg.c) //undefined
//使用with語法逻澳,打破了作用域規(guī)則,能改變{}內(nèi)自由變量的查找方式
//將自由變量當(dāng)做obj的屬性來查找
with(obj){
console.log(a) //1
console.log(b) //2
console.log(c) //報錯
}
1.模板不是html暖呕,因為包含一些指令斜做、插值,直接放在瀏覽器里是不能執(zhí)行的
2.html是標(biāo)記性語言湾揽,只有js才能實現(xiàn)判斷循環(huán)
3.因此瓤逼,模板一定是轉(zhuǎn)換成js代碼,即模板編譯
模板編譯流程
- 模板編譯為rander函數(shù)库物,執(zhí)行rander函數(shù)返回vnode
- 基于vnode再執(zhí)行patch和diff
- 使用webpack vue-loader霸旗,會在開發(fā)環(huán)境下編譯模板
使用rander代替template
Vue.component('component',{
rander: function(createElement){
return createElment( //vnode
'h'+this.level,
[
createElement('a',{
attrs:{
name: 'headerId',
href: '#'+'headerId'
}
},'this is a tag')
]
)
}
})
模板編譯總結(jié)
- with語法
- 模板到rander函數(shù),再到vnode艳狐,再到渲染和更新
- vue組件可以使用rander替代template
組件 渲染/更新過程
涉及vue原理三大知識點:
1.響應(yīng)式:監(jiān)聽data屬性 getter setter
2.模板編譯: 模板到rander函數(shù) 再到vnode
3.vdom: patch(elm,vnode)和patch(vnode,newVnode)
1. 初次渲染過程
1.解析模板為rander函數(shù)(在webpack的vue-loader定硝、vue-cli環(huán)境下已完成)
2.觸發(fā)響應(yīng)式,監(jiān)聽data屬性getter setter
3.執(zhí)行rander函數(shù)生成vnode毫目,執(zhí)行patch(elem,vnode)
2. 更新過程
1.修改data,觸發(fā)setter(此前在getter已經(jīng)被監(jiān)聽)
2.重新執(zhí)行rander函數(shù)生成newVnode诲侮,執(zhí)行patch(vnode,newVnode)
3. 異步渲染
vue是異步渲染镀虐,修改data一次性提交,能提高性能
$nextTick相關(guān)