Vue2.x響應(yīng)式簡單講解及示例
一、回顧Vue響應(yīng)式用法
vue響應(yīng)式,我們都很熟悉了。當(dāng)我們修改vue中data對(duì)象中的屬性時(shí),頁面中引用該屬性的地方就會(huì)發(fā)生相應(yīng)的改變。避免了我們再去操作dom,進(jìn)行數(shù)據(jù)綁定。
二、Vue響應(yīng)式實(shí)現(xiàn)分析
對(duì)于vue的響應(yīng)式原理,官網(wǎng)上給了出文字描述 https://cn.vuejs.org/v2/guide/reactivity.html 。
vue內(nèi)部主要是通過數(shù)據(jù)劫持和觀察者模式實(shí)現(xiàn)的
數(shù)據(jù)劫持:
vue2.x內(nèi)部使用Object.defineProperty https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
vue3.x內(nèi)部使用的Proxy https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
觀察者模式:http://www.dhdzp.com/article/219790.htm
內(nèi)部成員示意圖

各個(gè)成員的功能
Vue:
把data中的成員注入到Vue實(shí)例中,并把data中的成員轉(zhuǎn)換為getter和setter
Observer:
對(duì)data對(duì)象中的簡單類型數(shù)據(jù)及對(duì)象進(jìn)行監(jiān)聽,當(dāng)數(shù)據(jù)發(fā)生變化時(shí)通知Dep
Compiler:
解析每個(gè)元素中的指令/差值表達(dá)式,并替換成相應(yīng)的數(shù)據(jù)
Dep:
觀察者模式中的通知者,添加觀察者,當(dāng)數(shù)據(jù)變化時(shí)通知觀察者
Watcher:
每個(gè)引用data中的屬性的地方都有一個(gè)watcher對(duì)象,負(fù)責(zé)更新視圖
附:data對(duì)象中的屬性充當(dāng)被觀察者,引用data對(duì)象中屬性的地方充當(dāng)觀察者
三、Vue響應(yīng)式源碼實(shí)現(xiàn)
Vue對(duì)象實(shí)現(xiàn)
功能
- 負(fù)責(zé)接受初始化的參數(shù)
- 把data中的屬性注入到data實(shí)例,轉(zhuǎn)換成getter和setter
- 調(diào)用Observer監(jiān)聽data中所有屬性的變化
- 調(diào)用compiler解析指令、差值表達(dá)式.
class Vue{
constructor(options){
// 1、通過屬性保存穿進(jìn)來的屬性
this.$options= options||{};
this.$data= options.data||{};
this.$el = typeof options.el ==='string' ? document.querySelector(options.el) : options.el;
// 2、把data參數(shù)中的數(shù)據(jù)轉(zhuǎn)換為getter和setter 掛載到Vue實(shí)例上
this._proxyData(this.$data)
// 3、調(diào)用observe對(duì)象監(jiān)視data數(shù)據(jù)的變化
new Observer(this.$data)
// 4、調(diào)用compiler對(duì)象渲染頁面
new Compiler(this)
}
_proxyData(data){
if (data&&Object.keys(data).length>0){
for (const key in data) {
Object.defineProperty(this,key,{
configurable:true,
enumerable:true,
get(){
return data[key]
},
set(value){
if (data[key]===value) {
return;
}
data[key]=value;
}
})
}
}
}
}
Observer對(duì)象實(shí)現(xiàn)
功能
- 把data選項(xiàng)中的屬性進(jìn)行數(shù)據(jù)劫持
- data中的某個(gè)屬性也是對(duì)象的話,進(jìn)行遞歸轉(zhuǎn)換成響應(yīng)式對(duì)象
- 數(shù)據(jù)變化發(fā)送通知
//數(shù)據(jù)劫持 class Observer {
constructor(data) {
this.walk(data)
}
walk(data) {
//1、判斷data是否是對(duì)象
if (!data || typeof data !== 'object') {
return
}
//2、循環(huán)調(diào)用defineReactive進(jìn)行數(shù)據(jù)劫持
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
defineReactive(obj, key, val) {
//創(chuàng)建通知者
const dep = new Dep()
//使用walk把引用對(duì)象中的屬性變成響應(yīng)式的
this.walk(val)
const that=this;
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get() {
//通知者收集觀察者
Dep.target && dep.addSub(Dep.target)
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal;
that.walk(newVal)
//被觀察者發(fā)生變化的時(shí)候,通知者對(duì)象給每個(gè)觀察者發(fā)送通知
dep.notify()
}
})
}
}
Compile對(duì)象實(shí)現(xiàn)
功能
- 負(fù)責(zé)編譯模板,解析指令、差值表達(dá)式
- 負(fù)責(zé)頁面首次渲染
- 當(dāng)數(shù)據(jù)發(fā)生改變后,負(fù)責(zé)重新渲染視圖
//編譯器 class Compiler {
constructor(vm) {
this.el = vm.$el;
this.vm = vm;
this.compile(this.el)
}
//編譯模板 判斷節(jié)點(diǎn)是文本節(jié)點(diǎn)還是元素節(jié)點(diǎn)
compile(el) {
let childNodes = el.childNodes;
//處理第一層子節(jié)點(diǎn)
Array.from(childNodes).forEach(node => {
if (this.isTextNode(node)) {
this.compileText(node)
} else if (this.isElementNode(node)) {
this.compileElement(node)
}
//如果當(dāng)前節(jié)點(diǎn)還有子節(jié)點(diǎn) 遞歸調(diào)用編譯指令
if (node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
//編譯元素節(jié)點(diǎn),處理指令
compileElement(node) {
//遍歷所有的指令
Array.from(node.attributes).forEach(attr => {
//判斷是不是指令節(jié)點(diǎn)
if (this.isDirective(attr.name)) {
const nodeName = attr.name;
const key = attr.nodeValue;
const directive = nodeName.substr(2)
this.updater(directive,node,key)
}
})
}
updater(directive,node,key){
const updaterFn = this[directive+"Updater"]
updaterFn && updaterFn.call(this,node,this.vm[key],key)
}
//v-text
textUpdater(node,value,key){
node.textContent=value
//使用v-text表達(dá)式的地方就是一個(gè)觀察者
new Watcher(this.vm,key,newValue => {
node.textContent = newValue
})
}
//v-model
modelUpdater(node,value,key){
node.value =value
//使用v-model表達(dá)式的地方就是一個(gè)觀察者
new Watcher(this.vm,key,newValue => {
node.value = newValue
})
//實(shí)現(xiàn)雙向綁定
node.addEventListener('input',()=>{
this.vm[key] = node.value
})
}
//v-html
htmlUpdater(node,value,key){
node.innerHTML = value
//使用v-html表達(dá)式的地方就是一個(gè)觀察者
new Watcher(this.vm,key,newValue => {
node.innerHTML = newValue
})
}
//處理差值表達(dá)式
compileText(node) {
//匹配差值表達(dá)式的正則
let reg = /\{\{(.+?)\}\}/
//用正則匹配node的textContent,如果匹配到了 就替換
if (reg.test(node.textContent)) {
//獲取插值表達(dá)式的key
let key = RegExp.$1;
let value = node.textContent;
node.textContent = value.replace(reg, this.vm[key])
//使用差值表達(dá)式的地方就是一個(gè)觀察者
new Watcher(this.vm,key,newValue => {
node.textContent = newValue
})
}
}
//是否是指令
isDirective(attrName) {
return attrName.startsWith('v-')
}
//是否是文本節(jié)點(diǎn)
isTextNode(node) {
return node.nodeType === 3
}
//是否是元素
isElementNode(node) {
return node.nodeType === 1
}
}
Dep對(duì)象實(shí)現(xiàn)
功能
- 收集依賴,添加觀察者
- 通知所有觀察者
//通知者類 class Dep {
constructor() {
//存儲(chǔ)觀察者
this.subs = []
}
/**
* 收集觀察者
*/
addSub(sub) {
if (sub && sub.update) {
this.subs.push(sub)
}
}
/**
* 通知觀察者改變狀態(tài)
*/
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
Watcher對(duì)象實(shí)現(xiàn)
功能
- 當(dāng)數(shù)據(jù)變化時(shí),Dep通知所有Watcher實(shí)例更新視圖
- 自身實(shí)例化的時(shí)候往Dep對(duì)象中添加自己
//觀察者類 class Watcher {
constructor (vm,key,cb) {
//Vue實(shí)例
this.vm =vm;
// data中的key對(duì)象
this.key =key;
// 更新視圖的回調(diào)函數(shù)
this.cb = cb
//把當(dāng)前觀察者實(shí)例存放在Dep的target靜態(tài)屬性中
Dep.target =this
//觸發(fā)Observe的getter方法,把當(dāng)前實(shí)例存放在Dep.subs中
//data中key對(duì)應(yīng)的舊值
this.oldValue = this.vm[this.key]
Dep.target = null
}
//每個(gè)觀察者都有一個(gè)update方法來改變狀態(tài)
update(){
const newValue = this.vm[this.key]
if ( this.newValue === this.oldValue ) {
return
}
this.cb(newValue)
}
}
測試
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>index</title>
<script src="./js/dep.js"></script>
<script src="./js/watcher.js"></script>
<script src="./js/compiler.js"></script>
<script src="./js/observer.js"></script>
<script src="./js/vue.js"></script>
</head>
<body>
<p id="app">
<h1>差值表達(dá)式</h1>
<h3>{{msg}}</h3>
<h3>{{count}}</h3>
<h1>v-text</h1>
<p v-text='msg'></p>
<h1>v-model</h1>
<input type="text" v-model="msg" attr="msg">
<input type="text" v-model="count">
<h1>v-html</h1>
<p v-html="htmlText"></p>
</p>
<script>
let vm = new Vue({
el:"#app",
data:{
msg:'信息',
count:'數(shù)量',
person:{name:'張三'},
htmlText:"<p style='color:red'>你好</p>"
}
})
</script>
</body>
到此這篇關(guān)于Vue2.x響應(yīng)式簡單講解及示例的文章就介紹到這了,更多相關(guān)Vue2.x響應(yīng)式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue實(shí)現(xiàn)點(diǎn)擊展開點(diǎn)擊收起效果
這篇文章主要介紹了vue實(shí)現(xiàn)點(diǎn)擊展開,點(diǎn)擊收起效果,首先我們需要定義data里面的數(shù)據(jù),使用computed對(duì)data進(jìn)行處理,需要的朋友可以參考下2018-04-04
如何使用fetchEventSource實(shí)現(xiàn)sse流式請求
這篇文章主要介紹了如何使用fetchEventSource實(shí)現(xiàn)sse流式請求問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
vue使用微信JS-SDK實(shí)現(xiàn)分享功能
這篇文章主要介紹了vue使用微信JS-SDK實(shí)現(xiàn)分享功能,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08
解決vue打包 npm run build-test突然不動(dòng)了的問題
這篇文章主要介紹了解決vue打包 npm run build-test突然不動(dòng)了的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-11-11
vue+VeeValidate 校驗(yàn)范圍實(shí)例詳解(部分校驗(yàn),全部校驗(yàn))
validate()可以指定校驗(yàn)范圍內(nèi),或者是全局的 字段。而validateAll()只能校驗(yàn)全局。這篇文章主要介紹了vue+VeeValidate 校驗(yàn)范圍(部分校驗(yàn),全部校驗(yàn)) ,需要的朋友可以參考下2018-10-10
vite+vue3.0+ts+element-plus快速搭建項(xiàng)目的實(shí)現(xiàn)
本文將結(jié)合實(shí)例代碼,介紹vite+vue3.0+ts+element-plus快速搭建項(xiàng)目的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06

