1. 双向绑定的原理
双向绑定是MVVM架构的核心特性之一。在MVVM中,数据发生变化时视图也会更新,视图发生变化时数据会更新。
2. ViewModel的工作原理
ViewModel主要的两个部分
- 监听器(Observer):负责监听模型数据的变化。
- 解析器(Compiler):负责解析视图中的指令,并根据指令模板替换数据,同时绑定更新函数。
3. Vue中双向绑定的实现
- 初始化Vue实例,对数据进行响应化处理,实现一个监听器
Observer
,用来劫持并监听响应式数据的所有属性,如果属性有变化,就通知Dep
订阅者。 - 编译模板,找到动态绑定的数据,并初始化视图。
- 定义更新函数和
Watcher
,可以收到属性的变化通知并执行相应的方法,用于数据变化时更新视图。 - 使用
Dep
管理多个Watcher,确保数据变化时能够通知所有相关的Watcher。 - 实现解析器
Compile
,可以解析每个节点的相关指令,对模板数据和订阅器进行初始化。
4. 订阅器Dep实现
1. 发布-订阅者模式
发布-订阅者模式又叫观察者模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态改变时,所有依赖于它的对象都将得到通知。
2. 手写发布-订阅者模式
class EventBus {
constructor() {
this.subscribers = {};
}
subscribe(event, callback) {
if (!this.subscribers[event]) {
this.subscribers[event] = [];
}
this.subscribers[event].push(callback);
return () => this.unsubscribe(event, callback);
}
unsubscribe(event, callback) {
if (this.subscribers[event]) {
this.subscribers[event] = this.subscribers[event].filter(cb => cb !== callback);
}
}
publish(event, data) {
if (this.subscribers[event]) {
this.subscribers[event].forEach(callback => callback(data));
}
}
}
- 实现双向绑定的关键代码
Class Vue{
constructor(options){
this.$options = options || {};
this.$data = options.data;
}
}
function observe(obj) {
if (typeof obj !== 'object' || obj === null) {
return
}
new Observer(obj)
}
class Observer {
constructor(value) {
this.value = value
this.walk(value)
}
walk(obj) {
Object.keys(obj).forEach(key => {
defineReactive(obj, key, obj[key])
})
}
}
Class Compile{
constructor(el, vm) {
this.$vm = vm
this.$el = document.querySelector(el)
if(this.$el){
this.compile(this.$el);
}
}
compile(el) {
const childNodes = el.childNodes;
Array.from(childNodes).forEach(node => {
if(this.isElement(node)){
console.log("编译元素"+node.nodeName)
}else if(this.isInterpolation(node)){
console.log("编译插值"+node.textContent)
}
if(node.childNodes && node.childNodes.length > 0){
this.compile(node)
}
})
}
isElement(node){
return node.nodeType === 1
}
isInterpolation(node){
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
}
}
Class Watcher{
constructor(vm, key, updater) {
this.vm = vm;
this.key = key;
this.updaterFn = updater;
Dep.target = this;
vm[key];
Dep.target = null;
}
update(){
this.updaterFn.call(this.vm,this.vm[this.key])
}
}
Class Dep{
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
function defineReactive(obj, key, val) {
this.observe(val);
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
if (Dep.target) {
dep.addSub(Dep.target)
}
return val
},
set: function reactiveSetter(newVal) {
if (val === newVal) {
return
}
val = newVal
}
})
}