Vue中双向绑定的原理

34 阅读2分钟

csdn学习

掘金学习

1. 双向绑定的原理

双向绑定是MVVM架构的核心特性之一。在MVVM中,数据发生变化时视图也会更新,视图发生变化时数据会更新。

2. ViewModel的工作原理

ViewModel主要的两个部分

  • 监听器(Observer):负责监听模型数据的变化。
  • 解析器(Compiler):负责解析视图中的指令,并根据指令模板替换数据,同时绑定更新函数。

3. Vue中双向绑定的实现

  1. 初始化Vue实例,对数据进行响应化处理,实现一个监听器Observer,用来劫持并监听响应式数据的所有属性,如果属性有变化,就通知Dep订阅者。
  2. 编译模板,找到动态绑定的数据,并初始化视图。
  3. 定义更新函数和Watcher,可以收到属性的变化通知并执行相应的方法,用于数据变化时更新视图。
  4. 使用Dep管理多个Watcher,确保数据变化时能够通知所有相关的Watcher。
  5. 实现解析器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));

        }

    }

}
  1. 实现双向绑定的关键代码
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
        }
    })
}