一、Vue-cli構建的項目目錄
二凹耙、Vue雙向數(shù)據(jù)綁定
原理
Vue
遍歷data
對象中的所有數(shù)據(jù)姿现,使用Object.defineProperty
,將這些屬性在getter/setter中處理肖抱。當屬性被訪問時备典,將所有依賴該數(shù)據(jù)的函數(shù)部分收集起來,當屬性被修改時意述,通知watcher更新依賴該屬性的部分提佣。
不涉及視圖層,僅在js中簡單實現(xiàn)數(shù)據(jù)動態(tài)響應的功能
//思想:在getter中收集依賴荤崇,在setter中運行依賴拌屏,更新數(shù)據(jù)
let data={price:6,nums:3,total:0},target=null//目標函數(shù)
//創(chuàng)建一個依賴,功能:保存對數(shù)據(jù)依賴的函數(shù)术荤,運行依賴data中數(shù)性的函數(shù)
class Dep{
constructor(){
this.subscribers=[]//訂閱者倚喂,訂閱對data屬性有關聯(lián)的函數(shù)等
}
depend(){//收集關聯(lián)函數(shù),到訂閱者
if(target&&!this.subscribers.includes(target)){
this.subscribers.push(target)//同一個目標喜每,只能訂閱一次
}
}
notify(){//數(shù)據(jù)變化,通知關聯(lián)的依賴更新雳攘,也就是重新調(diào)用函數(shù)带兜,運行
this.subscribers.forEach(sub=>sub())
}
}
//遍歷屬性,監(jiān)控
Object.keys(data).forEach((key)=>{
let internalValue=data[key],dep=new Dep()
Object.defineProperty(data,key,{
//訪問屬性吨灭,會自動調(diào)用該函數(shù)
get(){
dep.depend()//訂閱關聯(lián)函數(shù)
return internalValue
},
//修改屬性
set(val){
internalValue=val
dep.notify()//更新依賴
}
})
})
function watcher(fn){
target=fn
target()//target中如果訪問或修改data中的屬性刚照,那么將會自動調(diào)用getter和setter
target=null
}
//調(diào)用函數(shù),開啟觀察
watcher(()=>{
data.total=data.price*data.nums
})
控制臺內(nèi)輸入喧兄,查看結(jié)果:
參考資料:【譯】Vue.js是如何做到數(shù)據(jù)響應的无畔?
三啊楚、VirtualDOM與diff
什么是虛擬DOM
在真實的dom中修改其一部分,可能會引起整個dom的波動浑彰,牽一發(fā)而動全身恭理,開銷較大,那么虛擬dom就是為了改變這種大開銷而提出來的郭变,他是js對象颜价,它的目的是最小限度的修改真是的dom,來實現(xiàn)這個過程的就是diff算法诉濒。
diff算法只會在同層比較周伦,不會跨層級比較,并且每次比較的結(jié)果未荒,會立馬在真實的dom中體現(xiàn)专挪,不是都比較完了,在一次性改變真實的dom
diff算法步驟
中心思想
遍歷newVdom中的節(jié)點片排,找到它在oldVdom中的位置寨腔,如果找到了就移動對應(dom:表示真實)dom元素,如果沒有找到划纽,就說明是新增加的節(jié)點脆侮,那么就新建節(jié)點插入dom。如果newVdom遍歷結(jié)束了勇劣,oldVdom還有沒有處理過的節(jié)點靖避,就說明這些節(jié)點在newVdom中被刪除了,那么刪除即可比默。
vue中的diff
遍歷通過在newVdom和oldVdom中設置頭指針和尾指針來實現(xiàn)幻捏,通過移動指針來比較。處理的節(jié)點會標記為已處理(標記的方法有2種命咐,當節(jié)點正好在vdom的指針處篡九,移動指針將它排除到未處理列表之外即可,否則就要采用其他方法醋奠,Vue的做法是將節(jié)點設置為undefined榛臼。
)
節(jié)點的比較有5種情況
1、if (oldVnode === vnode)
窜司,他們的引用一致沛善,可以認為沒有變化。
2塞祈、if(oldVnode.text !== null && vnode.text !== null && oldVnode.text !== vnode.text)
金刁,文本節(jié)點的比較,需要修改,則會調(diào)用Node.textContent = vnode.text尤蛮。
3媳友、if( oldCh && ch && oldCh !== ch )
, 兩個節(jié)點都有子節(jié)點,而且它們不一樣产捞,這樣我們會調(diào)用updateChildren函數(shù)比較子節(jié)點醇锚,這是diff的核心.
4、else if (ch)
轧葛,只有新的節(jié)點有子節(jié)點搂抒,調(diào)用createEle(vnode),vnode.el已經(jīng)引用了老的dom節(jié)點尿扯,createEle函數(shù)會在老dom節(jié)點上添加子節(jié)點求晶。
5、else if (oldCh)
衷笋,新節(jié)點沒有子節(jié)點芳杏,老節(jié)點有子節(jié)點,直接刪除老節(jié)點辟宗。
diff核心
1爵赵、首先新舊頭、尾兩兩比較(new_s:old_s泊脐,new_s:old_e空幻,new_e:old_s,new_e:old_e)共四種容客。
處理了這些場景之后秕铛,一方面一些不需要做移動的DOM得到快速處理,另一方面待處理節(jié)點變少缩挑,縮小了后續(xù)操作的處理范圍但两,性能也得到提升
2、如果過程1中供置,不滿足節(jié)點相等谨湘,又分兩種情況:
- 結(jié)點設置了key
設key后,會從用key生成的對象oldKeyToIdx中查找匹配的節(jié)點芥丧,所以為節(jié)點設置key可以更高效的利用dom紧阔。這也就是v-for中要設置key的原因- 沒有設置key
直接創(chuàng)建新節(jié)點,刪除舊節(jié)點续担。不能復用
下面舉個例子擅耽,畫出diff完整的過程,每一步dom的變化都用不同顏色的線標出赤拒。
參考資料:解析vue2.0的diff算法
深入Vue2.x的虛擬DOM diff原理
四秫筏、Vuex
1、什么是vuex
vuex是用來管理各個組件的共享數(shù)據(jù)的挎挖,它可以看做是一個倉庫这敬,倉庫里包含了State、Actions和Mutations蕉朵,當其中一個組件更改了State中的共享數(shù)據(jù)時崔涂,也會反映在其他組件中。
使用場景:開發(fā)大型單頁面應用始衅,各個組件之間有很多共享數(shù)據(jù)冷蚂。否則可以使用總線。
在vue-cli構建的項目中使用方式
1汛闸、新建store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)//使用插件的寫法
export default new Vuex.Store({//創(chuàng)建一個倉庫蝙茶,倉庫里有State、actions和mutations
state: {
city: '上海'//共享數(shù)據(jù)
} ,
actions: {
changeCity(ctx, city){
ctx.commit('Xiugaicity', city)//通過commit觸發(fā)mutations修改數(shù)據(jù)诸老,‘xiugaicity’是自定義的觸發(fā)事件
}
},
mutations: {
xiugaicity(state, city){
state.city = city
}
}
})
2隆夯、在main.js中引入,在vue實例中添加store
import store from './store/index.js'
var vm=new Vue({
el: '#app',
// router必須有别伏,否則app中的使用router-view會報錯
router,
store,
// 這里的App是從import App from './App'這里倒入的變量蹄衷,可以更改
components: { App },
template: '<App/>'
})
3、在組件中使用厘肮,在需要修改城市的地方調(diào)用dipatch,觸發(fā)actions中的行為
this.$store.dispatch('changeCity',city)//'changeCity'自定義事件愧口,與actions中的事件一致
如果只有同步操作,修改state中的數(shù)據(jù)的時候类茂,可以不dispatch驚動actions耍属,只需要
this.$store.commit('xiugaicity',city)
,越過actions直接commit觸發(fā)mutations更改數(shù)據(jù)。
4大咱、優(yōu)化代碼
//對于state中的屬性每次通過this.$store.state.city來獲取太麻煩恬涧,
//借助mapState,將state中的屬性映射為組件的this.city屬性,對于mutations等碴巾,
//同樣有mapMutations 溯捆,將this.$store.commit('xiugaicity',city)映射為this.xiugaicity方法
// 在單獨構建的版本中輔助函數(shù)為 Vuex.mapState
import { mapState } from 'vuex'
computed: {
localComputed () { /* ... */ },
// 使用對象展開運算符可以和組件中的其他計算屬性并存
...mapState({
city:city
})
}
五、Vue組件通信
1厦瓢、通過Prop
向子組件傳遞數(shù)據(jù)
父組件在子組件實例中提揍,通過定義一個屬性:a=“message”,希望子組件接受這個信息煮仇,子組件通過props來接收這個屬性劳跃。
<body>
<div id="app">//cnt是要傳遞的信息的包裝屬性,count代表的值才是真正的信息
<button-counter :cnt="count"></button-counter>
</div>
</body>
<script>
Vue.component('button-counter',{
data:function(){
return{
count:this.cnt
}
},
props:['cnt'],//接收cnt屬性浙垫,不要直接改變父組件傳遞過來的cnt刨仑,需要復制一下郑诺,否則會報錯
template:`<button @click="count++">Click{{count}}</button>`
})
new Vue({
el:'#app',
data:{
count:0
}
})
</script>
props書寫方式:
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
props: { title: String, likes: Number, isPublished: Boolean, commentIds: Array, author: Object }
props特性,對應的屬性值不會在dom中顯示杉武,非props特性會在dom結(jié)構中顯示辙诞,子組件使用非props傳過來的值,會報錯轻抱,不使用則不會報錯飞涂。
2、通過事件向父級組件發(fā)送消息
子組件通過$emit('event',value)向父級組件拋出事件且傳值祈搜,父級組件通過@event="函數(shù)名或者js表達式"來執(zhí)行较店。
1、父組件通過js表達式接受
<body>
<div id="app">
<button-counter @addcount="count+=$event"></button-counter>
<div>count:{{count}}</div>
</div>
</body>
<script>
Vue.component('button-counter',{
data:function(){
return{
}
},
template:`<button @click="$emit('addcount',1)">Click add count</button>`
})
new Vue({
el:'#app',
data:{
count:0
}
})
</script>
2容燕、在methods中處理
<body>
<div id="app">
<button-counter @addcount="addcount"></button-counter>
<div>count:{{count}}</div>
</div>
</body>
<script>
Vue.component('button-counter',{
data:function(){
return{
}
},
template:`<button @click="add">Click add count</button>`,
methods:{
add(){
this.$emit('addcount',1)
}
}
})
new Vue({
el:'#app',
data:{
count:0
},
methods:{
addcount(val){
this.count+=val
}
}
})
</script>
3梁呈、使用bus總線,組件之間傳值
<body>
<div id="app">
<button-counter :name='bob'></button-counter>
<button-counter :name='ali'></button-counter>
</div>
</body>
<script>
Vue.prototype.bus=new Vue()//使用bus總線蘸秘,bus本身就是vue實例
Vue.component('button-counter',{
data:function(){
return{
n:this.name
}
},
props:['name'],
template:`<button @click="change">name:{{this.n}}</button>`,
methods:{
change(){
this.bus.$emit('addcount',this.n)
}
},
mounted(){//在各個組件mounted中監(jiān)聽組件傳值
var this_=this
this.bus.$on('addcount',function(n){
this_.n=n
})
}
})
new Vue({
el:'#app',
data:{
bob:'bob',
ali:'dfd'
}
})
</script>
4捧杉、使用vuex來管理組件的共享數(shù)據(jù)。
MVC|MVP|MVVM
1秘血、MVC
視圖(View):
用戶界面
味抖。
控制器(Controller):業(yè)務邏輯
模型(Model):數(shù)據(jù)保存
通信方式
:
1、View 傳送指令到 Controller
2灰粮、Controller 完成業(yè)務邏輯后仔涩,要求 Model 改變狀態(tài)
3、 Model 將新的數(shù)據(jù)發(fā)送到 View粘舟,用戶得到反饋
2熔脂、MVP
MVP 模式將 Controller 改名為 Presenter,同時改變了通信方向柑肴。
1.各部分之間的通信霞揉,都是雙向的。
- View 與 Model 不發(fā)生聯(lián)系晰骑,都通過 Presenter 傳遞适秩。
- View 非常薄,不部署任何業(yè)務邏輯硕舆,稱為"被動視圖"(Passive View)秽荞,即沒有任何主動性,而 Presenter非常厚抚官,所有邏輯都部署在那里扬跋。
3、MVVM
MVVM 模式將 Presenter 改名為 ViewModel凌节,基本上與 MVP 模式完全一致钦听。
唯一的區(qū)別是洒试,它采用雙向綁定(data-binding):View的變動,自動反映在 ViewModel朴上,反之亦然儡司。