vue 雙向數(shù)據(jù)綁定的實(shí)現(xiàn)學(xué)習(xí)之監(jiān)聽器的實(shí)現(xiàn)方法
提到了vue實(shí)現(xiàn)的基本實(shí)現(xiàn)原理:Object.defineProperty() -數(shù)據(jù)劫持 和 發(fā)布訂閱者模式(觀察者),下面講的就是數(shù)據(jù)劫持在代碼中的具體實(shí)現(xiàn)。
1.先看如何調(diào)用
new一個(gè)對(duì)象,傳入我們的參數(shù),這個(gè)Myvue ,做了啥?

上面看到了在實(shí)例化一個(gè)Myvue 對(duì)象的時(shí)候,會(huì)執(zhí)行init方法, init 方法做了兩個(gè)事,調(diào)用了observer 方法,和 實(shí)例化調(diào)用了 compile 方法。 到這里我們就明白了,實(shí)例化一個(gè)Myvue后,我們要做的就是監(jiān)聽數(shù)據(jù)變化和編譯模板 。
上面Object.key() 方法,實(shí)例化時(shí)傳入的data里面對(duì)應(yīng)的變量緩存到 Myvue 對(duì)象的 $prop上,這樣方便在后續(xù)處理數(shù)據(jù)。怎么個(gè)方便法呢!...
2.observer 的實(shí)現(xiàn)
observer ,模式里面的角色定位 他是一個(gè)發(fā)布者,也可以理解為是一個(gè)觀察者
function observer (data) {
if(!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach(key => {
// 對(duì)每個(gè)屬性監(jiān)聽處理
defineReactive(data, key, data[key]);
})
}
defineReactive
function defineReactive (data,key,value) {
// 每次訪問(wèn)/修改屬性的時(shí)候 實(shí)例化一個(gè)調(diào)度中心Dep
var dep = new Dep();
Object.defineProperty(data,key,{
get: function() {
// 添加到watcher 的Dep 調(diào)度中心
if (Dep.target) { // Dep.target 是個(gè)什么鬼? 轉(zhuǎn)到watcher.js 它是某個(gè)訂閱者 watcher
dep.addSub(Dep.target); //這個(gè)代碼段的意思: 如果有訂閱者(訪問(wèn)/修改屬性的時(shí)候) 就將這個(gè)訂閱者統(tǒng)一放進(jìn) Dep 調(diào)度中心中
}
// console.log(`${key}屬性被訪問(wèn)了`)
return value
},
set: function (newValue) {
if (value != newValue) {
// console.log(`${key}屬性被重置了`)
value = newValue
dep.notify(); //我這里有做改動(dòng)了,通知調(diào)度中心的notify方法
}
}
})
// 遞歸調(diào)用,observe 這個(gè)value
observer(value)
}
Dep: 這里是所有訂閱者的一個(gè)調(diào)度中心,它不是直接監(jiān)聽 發(fā)布者的信息,發(fā)布者將要發(fā)布的信息 發(fā)布到 一個(gè)中介、調(diào)度中心(Dep),由這個(gè)Dep 來(lái)調(diào)度信息給哪個(gè)訂閱者(Watcher)
// 統(tǒng)一管理watcher訂閱者的Dep (調(diào)度中心) Dispatch center
function Dep () {
// 所有的watcher 放進(jìn)這里統(tǒng)一管理
this.subs = []
}
Dep.target = null;
// 通知視圖更新dom的 notify的方法
Dep.prototype.notify = function () {
// this.subs 是上面訂閱器watcher 的集合
this.subs.forEach(sub => {
// sub 是某個(gè)Watcher 具體調(diào)用某個(gè)Watcher的update 方法
sub.update()
})
}
// 添加訂閱者的方法
Dep.prototype.addSub = function (sub) {
this.subs.push(sub)
}
3.訂閱器Watcher
// 具體的訂閱器Watcher
// 傳入一個(gè)vue 的實(shí)例, 監(jiān)聽的屬性, 以及處理的回調(diào)函數(shù)
function Watcher (vm,prop,callback) {
this.vm = vm;
this.$prop = prop;
this.value = this.get();
this.callback = callback; // 具體watcher所具有的方法,不同的watcher 不同的回調(diào)函數(shù),處理不同的業(yè)務(wù)邏輯
}
// 添加watcher 獲得屬性的get 方法,當(dāng)有屬性訪問(wèn)/設(shè)置 的時(shí)候,就產(chǎn)生訂閱者 將這個(gè)訂閱者放進(jìn)調(diào)度中心
Watcher.prototype.get = function () {
Dep.target = this;
// 獲得屬性值
const value = this.vm.$data[this.$prop];
return value
}
// 添加watcher的更新視圖的方法
Watcher.prototype.update = function () {
// 當(dāng)屬性值有變化的時(shí)候,執(zhí)行方法,更新試圖
const value = this.vm.$data[this.$prop];
const oldValue = this.value;
// update 執(zhí)行的時(shí)候,先獲取 vm 中data實(shí)時(shí)更新的屬性值,this.value 是vm data中之前的老值
if (oldValue != value) {
// console.log('人家通知了,我要改變了')
// 把剛剛獲取的更新值賦給之前vm data 中的值
this.value = value
// 執(zhí)行回調(diào)函數(shù) 具體怎么處理這個(gè),看實(shí)際調(diào)用時(shí)候 callback 的處理
this.callback(this.value)
}
}
4.模板編譯
(為了直接看到頁(yè)面數(shù)據(jù)變化的效果,在模板編譯的核心數(shù)據(jù)處理上做了dom 操作,下一篇將講模板編譯的一些細(xì)節(jié)處理)
// dom模板編譯 vm 就是我們最上面的Myvue 對(duì)象
function Compile (vm) {
this.vm = vm;
this.$el = vm.el;
// this.data = vm.data;
this.fragment = null; // 用作后面模板引擎 創(chuàng)建文檔片段
this.init()
}
Compile.prototype = {
// init 方法簡(jiǎn)單處理,直接做dom 操作,后面會(huì)用詳細(xì)的模板引擎的學(xué)習(xí)
init: function () {
let value = this.vm.$data.name // 初始化獲取到的值 放進(jìn)dom節(jié)點(diǎn)中
document.querySelector('.form-control').value = value;
document.querySelector('.template').textContent = value
// 通知訂閱者更新dom
new Watcher(this.vm,this.vm.$prop, (value) => {
document.querySelector('.form-control').value = value;
document.querySelector('.template').textContent = value
})
document.querySelector('.form-control').addEventListener('input',(e) => {
let targetValue = e.target.value
if(value !== targetValue) {
this.vm.$data.name = e.target.value // 將修改的值 更新到 vm的data中
document.querySelector('.form-control').value = targetValue; // 更新dom 節(jié)點(diǎn)
document.querySelector('.template').textContent = targetValue
}
},false)
}
}
這樣就可以看到 在表單中,數(shù)據(jù)的雙向綁定了。

未完待續(xù),錯(cuò)誤之處,敬請(qǐng)指出,共同進(jìn)步!
下一篇 vue 雙向數(shù)據(jù)綁定的實(shí)現(xiàn)學(xué)習(xí)(三)- 模板編譯
附:演示代碼:
js:
function Myvue (options) {
this.$options = options
this.$el = document.querySelector(options.el);
this.$data = options.data;
Object.keys(this.$data).forEach(key => {
this.$prop = key;
})
this.init()
}
Myvue.prototype.init = function () {
// 監(jiān)聽數(shù)據(jù)變化
observer(this.$data);
// 獲得值
// let value = this.$data[this.$prop];
// 不經(jīng)過(guò)模板編譯直接 通知訂閱者更新dom
// new Watcher(this,this.$prop,value => {
// console.log(`watcher ${this.$prop}的改動(dòng),要有動(dòng)靜了`)
// this.$el.textContent = value
// })
//通知模板編譯來(lái)執(zhí)行頁(yè)面上模板變量替換
new Compile(this)
}
function observer (data) {
if(!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach(key => {
// 對(duì)每個(gè)屬性監(jiān)聽處理
defineReactive(data, key, data[key]);
})
}
function defineReactive (data,key,value) {
// 每次訪問(wèn)/修改屬性的時(shí)候 實(shí)例化一個(gè)調(diào)度中心Dep
var dep = new Dep();
Object.defineProperty(data,key,{
get: function() {
// 添加到watcher 的Dep 調(diào)度中心
if (Dep.target) { // Dep.target 是個(gè)什么鬼? 轉(zhuǎn)到watcher.js 它是某個(gè)訂閱者 watcher
dep.addSub(Dep.target); //這個(gè)代碼段的意思: 如果有訂閱者(訪問(wèn)/修改屬性的時(shí)候) 就將這個(gè)訂閱者統(tǒng)一放進(jìn) Dep 調(diào)度中心中
}
// console.log(`${key}屬性被訪問(wèn)了`)
return value
},
set: function (newValue) {
if (value != newValue) {
// console.log(`${key}屬性被重置了`)
value = newValue
dep.notify(); //我這里有做改動(dòng)了,通知調(diào)度中心的notify方法
}
}
})
// 遞歸調(diào)用,observe 這個(gè)value
observer(value)
}
// 統(tǒng)一管理watcher訂閱者的Dep (調(diào)度中心) Dispatch center
function Dep () {
// 所有的watcher 放進(jìn)這里統(tǒng)一管理
this.subs = []
}
Dep.target = null;
// 通知視圖更新dom的 notify的方法
Dep.prototype.notify = function () {
// this.subs 是上面訂閱器watcher 的集合
this.subs.forEach(sub => {
// sub 是某個(gè)Watcher 具體調(diào)用某個(gè)Watcher的update 方法
sub.update()
})
}
// 添加訂閱者的方法
Dep.prototype.addSub = function (sub) {
this.subs.push(sub)
}
// 具體的訂閱器Watcher
// 傳入一個(gè)vue 的示例, 監(jiān)聽的屬性, 以及處理的回調(diào)函數(shù)
function Watcher (vm,prop,callback) {
this.vm = vm;
this.$prop = prop;
this.value = this.get();
this.callback = callback; // 具體watcher所具有的方法,不同的watcher 不同的回調(diào)函數(shù),處理不同的業(yè)務(wù)邏輯
}
// 添加watcher 獲得屬性的get 方法,當(dāng)有屬性訪問(wèn)/設(shè)置 的時(shí)候,就產(chǎn)生訂閱者 將這個(gè)訂閱者放進(jìn)調(diào)度中心
Watcher.prototype.get = function () {
Dep.target = this;
// 獲得屬性值
const value = this.vm.$data[this.$prop];
return value
}
// 添加watcher的更新視圖的方法
Watcher.prototype.update = function () {
// 當(dāng)屬性值有變化的時(shí)候,執(zhí)行方法,更新試圖
const value = this.vm.$data[this.$prop];
const oldValue = this.value;
// update 執(zhí)行的時(shí)候,先獲取 vm 中data實(shí)時(shí)更新的屬性值,this.value 是vm data中之前的老值
if (oldValue != value) {
// console.log('人家通知了,我要改變了')
// 把剛剛獲取的更新值賦給之前vm data 中的值
this.value = value
// 執(zhí)行回調(diào)函數(shù) 具體怎么處理這個(gè),看實(shí)際調(diào)用時(shí)候 callback 的處理
this.callback(this.value)
}
}
// dom模板編譯 vm 就是我們最上面的Myvue 對(duì)象
function Compile (vm) {
this.vm = vm;
this.$el = vm.el;
// this.data = vm.data;
this.fragment = null; // 用作后面模板引擎 創(chuàng)建文檔片段
this.init()
}
Compile.prototype = {
// init 方法簡(jiǎn)單處理,直接做dom 操作,后面會(huì)用詳細(xì)的模板引擎的學(xué)習(xí)
init: function () {
let value = this.vm.$data.name // 初始化獲取到的值 放進(jìn)dom節(jié)點(diǎn)中
document.querySelector('.form-control').value = value;
document.querySelector('.template').textContent = value
// 通知訂閱者更新dom
new Watcher(this.vm,this.vm.$prop, (value) => {
document.querySelector('.form-control').value = value;
document.querySelector('.template').textContent = value
})
document.querySelector('.form-control').addEventListener('input',(e) => {
let targetValue = e.target.value
if(value !== targetValue) {
this.vm.$data.name = e.target.value // 將修改的值 更新到 vm的data中
document.querySelector('.form-control').value = targetValue; // 更新dom 節(jié)點(diǎn)
document.querySelector('.template').textContent = targetValue
}
},false)
}
}
html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue雙向綁定原理及實(shí)現(xiàn)</title>
<link rel="stylesheet" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
crossorigin="anonymous">
<style>
#app {
margin: 20px auto;
width: 400px;
padding: 50px;
text-align: center;
border: 2px solid #ddd;
}
</style>
</head>
<body>
<div id="app">
<input class="form-control" v-model="name" type="text">
<h1 class="template">{{name}}</h1>
</div>
<script src="./js/index1.js"></script>
<script>
const vm = new Myvue({
el: "#app",
data: {
name: "vue 雙向數(shù)據(jù)綁定test1"
}
});
</script>
</body>
</html>
總結(jié)
以上所述是小編給大家介紹的vue 雙向數(shù)據(jù)綁定的實(shí)現(xiàn)學(xué)習(xí)之監(jiān)聽器的實(shí)現(xiàn)方法,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
vue2老項(xiàng)目vite升級(jí)改造過(guò)程記錄
目前vite主要默認(rèn)是支持給vue3使用的,并且如果使用官方的cli創(chuàng)建的項(xiàng)目一樣會(huì)默認(rèn)使用vue3去構(gòu)建項(xiàng)目,此時(shí)對(duì)于一些vue2的老項(xiàng)目就顯得不友好了,下面這篇文章主要給大家介紹了關(guān)于vue2老項(xiàng)目vite升級(jí)改造的相關(guān)資料,需要的朋友可以參考下2022-12-12
解決echarts vue數(shù)據(jù)更新,視圖不更新問(wèn)題(echarts嵌在vue彈框中)
這篇文章主要介紹了解決echarts vue數(shù)據(jù)更新,視圖不更新問(wèn)題(echarts嵌在vue彈框中),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-07-07
Vue實(shí)現(xiàn)簡(jiǎn)單計(jì)算器案例
這篇文章主要為大家詳細(xì)介紹了Vue實(shí)現(xiàn)簡(jiǎn)單計(jì)算器案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02
Element中table組件(el-table)右側(cè)滾動(dòng)條空白占位處理
當(dāng)我設(shè)置了max-height,就會(huì)在表格右側(cè)出現(xiàn)一列空白的占位,本文主要介紹了Element中table組件(el-table)右側(cè)滾動(dòng)條空白占位處理,感興趣的可以了解一下2023-09-09
vue 彈出框 引入另一個(gè)vue頁(yè)面的示例代碼
這篇文章主要介紹了vue 彈出框引入另一個(gè)vue頁(yè)面,這種方式適用于在一個(gè)頁(yè)面邏輯比較多的時(shí)候,可以搞多個(gè)頁(yè)面,防止出錯(cuò),本文通過(guò)示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-08-08
vuex 實(shí)現(xiàn)getter值賦值給vue組件里的data示例
今天小編就為大家分享一篇vuex 實(shí)現(xiàn)getter值賦值給vue組件里的data示例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11

