平常在vue
中使用v-model
使用较多,下面来看一下实现简易原理。
创建全局ob视图,结合观察、订阅模式完成双向绑定。
<div id="app">
<input type="text" v-model='message' />
</div>
<script>
const app = new Reactive({
el: '#app',
data() {
return {
message: 'hello'
}
}
})
class Reactive {}
</script>
首先,把挂载点确定好,把id
为#app
传入类Reactive
中,data
是已经确定好的数据。
constructor({ el, data }) {
const _data = data();
this.el = document.querySelector(el);//设置挂载点
this._ob = this.createObserver();//创建视图
this.restoreProxy(_data);//设置代理对象
return this.proxy
}
constructor
接收到挂载点id
,和元数据。创建el挂载点给到Reactive
里边。
createObserver() {
const _this = this;
return {
watchers: {},//观察者列表,保存的映射关系
addWatchers(key, cb) {//添加任务视图,映射关系
if (this.watchers[key]) {
this.watchers[key].push(cb);
} else {//初始化的时候没有任务,先添加一个
this.watchers[key] = [cb];
}
},
subscribe(key) {//订阅
_this.el
.querySelectorAll(`[v-model="${key}"]`)
.forEach(item => {
const cb = text => item.value = text;//指令对应的任务方法
item.addEventListener('input', () => {
_this.proxy[key] = item.value;
});
cb(_this.proxy[key]);// 初始化数据,第一次调用任务方法
this.addWatchers(key, cb);//添加到队列里
});
},
}
}
创建ob任务视图,在任务视图里添加观察者列表,保存的映射关系。在具体订阅每个指令和节点之间的关系、指令处理方法。
通过querySelectorAll
的方式查询到挂载点下所有自定义属性v-mode
节点,本例中只有input
输入框节点,每次修改input
的value
即可,创建任务方法。
在把任务方法添加到观察者列表watchers
中去。
restoreProxy(data) {
//设置代理对象,构建关系
this.proxy = new Proxy(data, {
get: (target, key) => {
return target[key]
},
set: (target, key, value) => {
target[key] = value;
this._ob.emit(key);
return true;
}
});
for (let key in data) {
this._ob.subscribe(key);
}
}
设置代理对象,构建关系,接收第一次传入的初始数据data
,通过代理对象的形式设置getter
和setter
,让每一次的访问和修改都能察觉到,并作出处理。
迭代data
,去订阅data
与节点之间的关系,初始化任务绑定好数据。这个时候已经能看到input输入框里有hello
了。
我们在创建订阅任务时,给input绑定了一个oninput
事件,每次输入input
都会触发proxy
的set,更新了message
,在去更新与message
有关的节点。
createObserver() {//ob 任务队列
const _this = this;
return {
watchers: {},//观察者列表,保存的映射关系
addWatchers(key, cb) {//添加任务视图,映射关系
if (this.watchers[key]) {
this.watchers[key].push(cb);
} else {//初始化的时候没有任务,先添加一个
this.watchers[key] = [cb];
}
},
subscribe(key) {
_this.el
.querySelectorAll(`[v-model="${key}"]`)
.forEach(item => {
const cb = text => item.value = text;//指令对应的任务方法
item.addEventListener('input', () => {
_this.proxy[key] = item.value;// 这里会触发proxy 的set方法
});
cb(_this.proxy[key]);
this.addWatchers(key, cb);//添加到队列里
});
_this.el
.querySelectorAll(`[data-on="${key}"]`)
.forEach(item => {
const cb = text => item.innerHTML = text;//每一个不同的指令对应不同的任务方法
cb(_this.proxy[key])
this.addWatchers(key, cb);//添加到队列里
});
},
emit(key) {//添加需要更新的任务
this.task.add(key);
this.update();
},
update() {
// 异步刷新执行任务,不重复刷新视图。
if (this.isUpdate) { return }
this.isUpdate = true;
Promise.resolve().then(() => {
for (let key of this.task) {
this.watchers[key].forEach(cb => {
cb(_this.proxy[key]);
});
}
this.isUpdate = false;//恢复状态
this.task.clear();//清空任务
});
},
task: new Set(),// ES6set,数据不会重复
isUpdate: false,// 状态开关
}
}
在来创建一个data-on
的指令来验证是否会随着message
的变化而变化。也是同样的方试查找到节点,创建对应任务方法,放到观察队列里。
在修改message
时,某些情况下会去改变与之关联的数据,我们把所有需要改变的任务通过set方式收集在一起,最后在异步刷新数据。
完整代码如下:
<div id="app">
<div data-on="message"></div>
<input type="text" v-model='message' />
</div>
<script>
class Reactive {
constructor({ el, data }) {
const _data = data();
this.el = document.querySelector(el);
this._ob = this.createObserver();//创建视图
this.restoreProxy(_data);//设置代理对象
return this.proxy
}
createObserver() {//ob 任务队列
const _this = this;
return {
watchers: {},//观察者列表,保存的映射关系
addWatchers(key, cb) {//添加任务视图,映射关系
if (this.watchers[key]) {
this.watchers[key].push(cb);
} else {//初始化的时候没有任务,先添加一个
this.watchers[key] = [cb];
}
},
subscribe(key) {
_this.el
.querySelectorAll(`[v-model="${key}"]`)
.forEach(item => {
const cb = text => item.value = text;//指令对应的任务方法
item.addEventListener('input', () => {
_this.proxy[key] = item.value;// 这里会触发proxy 的set方法
});
cb(_this.proxy[key]);
this.addWatchers(key, cb);//添加到队列里
});
_this.el
.querySelectorAll(`[data-on="${key}"]`)
.forEach(item => {
const cb = text => item.innerHTML = text;//每一个不同的指令对应不同的任务方法
cb(_this.proxy[key])
this.addWatchers(key, cb);//添加到队列里
});
},
emit(key) {//添加需要更新的任务
this.task.add(key);
this.update();
},
update() {
// 异步刷新执行任务, 保证重复刷新视图。
if (this.isUpdate) { return }
this.isUpdate = true;
Promise.resolve().then(() => {
for (let key of this.task) {
this.watchers[key].forEach(cb => {
cb(_this.proxy[key]);
});
}
this.isUpdate = false;//恢复状态
this.task.clear();//清空任务
});
},
task: new Set(),// ES6set,保证数据不会重复
isUpdate: false,// 状态开关
}
}
restoreProxy(data) {
//设置代理对象,构建关系
this.proxy = new Proxy(data, {
get: (target, key) => {
return target[key]
},
set: (target, key, value) => {
target[key] = value;
this._ob.emit(key);
return true;
}
});
for (let key in data) {
this._ob.subscribe(key);
}
}
}
const app = new Reactive({
el: '#app',
data() {
return {
message: 'hello'
}
}
})
</script>
这个时候可以看到修改input
绑定的message,div
通过data-on
绑定的也会随之变化。