Vue源码解析--实现观察者OBserver

MVVM实现原理图

vue采用数据劫持配合订阅者和发布者模式的方式 ,

通过   Object.defineProperty 的setter 和  getter  对数据进行劫持 , 

在数据变化时, 发布消息给依赖器 Dep(订阅者),  去通知观察者Watcher  做出对应的回调函数 , 进行视图更新

MVVM 作为绑定入口 , 整合Observer , Compile 和Watcher  三者 , Observer来监听model数据变化 , 

Compile来解析编译模板指令 , 最终利用Watcher 搭建起 Compile , Observer , Watcher 之间的通信桥梁 , 

达到数据 变化=>  视图更新 ; 视图交互变化 => 数据model变更的双向绑定效果 

 OBserver.js

class Watcher { //观察者  作用  去观察新值与旧值是否有变化 ,如果有变化则更新
    constructor(vm, expr, cd) { //需要vm , expr 来获取旧值和新值是否有更新 , cd为回调 ,进行数据更新
        // 先将形参保存
        this.vm = vm;
        this.expr = expr;
        this.cd = cd;
        //先保存旧值
        this.oldVal = this.getOldVal()
    }
    getOldVal() { //获取旧值
        Dep.target = this; //new MVue()时建立watcher , 将watcher挂载到dep上
        const oldval = compileUtil.getVal(this.expr, this.vm);
        Dep.target = null; //获取到旧值后销毁 , 不然每次改变旧值添加新的观察者 , 不易维护
        return oldval
    }
    updata() { //作用 ,判断新值与旧值是否有变化 , 有变化则回调回去更新视图
        const newval = compileUtil.getVal(this.expr, this.vm);
        if (newval !== this.oldval) { //旧值与新值不相等
            this.cd(newval)
        }
    }
}
class Dep {
//作用 , 1.通知watcher更新 , 2.收集watcher constructor() { //收集依赖 , 不需要传参 this.subs = []; //用于存储观察者 } //收集观察者 addSub(watcher) { this.subs.push(watcher); } //通知观察者去更新 notify() { console.log('观察者', this.subs) //遍历数组 , 找到对应的观察者进行更新 this.subs.forEach(w => w.updata()) //观察者需要有更新视图的方法updata() } } class Observer { constructor(data) { this.observer(data) } observer(data) { //data为对象 , 此处只针对对象处理 if (data && typeof data === 'object') { //针对对象进行遍历 Object.keys(data).forEach(key => { this.defineRective(data, key, data[key]) }) } } defineRective(obj, key, value) { //递归遍历 this.observer(value) //劫持数据的时候创建收集依赖器 const dep = new Dep(); //劫持 Object.defineProperty(obj, key, { enumerable: true, //是否可遍历 configurable: false, //是否可更改编写 get() { //获取值时走这里 //订阅数据变化时 , 往Dep中添加观察者 Dep.target && dep.addSub(Dep.target) //watcher从哪来? //Dep =>>订阅器 //订阅消息=>绑定监听 发布消息 =>触发事件 return value }, set: (newVal) => { //设置值走这里 this.observer(newVal) if (newVal != value) { //如果旧值不等于新值 , 则将新值赋给旧值 value = newVal; } //更改数据后 通知观察者更新 dep.notify() } }) } }

 1. 初始化数据时 , 劫持所有属性  走Observer中  Object.defineProperty 的get方法   通知订阅者Dep去收集每个属性上绑定的观察者Watcher  , 通过dep.addSub  存放 subs 中 

 2.更改数据时 , 触发Observer 中Object.defineProperty 的set方法   ,  此时 通知订阅者Dep , 数据有变化 ,  订阅者通过   dep.notify()  ,  对存在subs 数组中属性绑定的观察者Watcher进行遍历 并触发Watcher.updata进行视图更新

需要注意:

1. 何时在所有属性上绑定观察者Wacther ?

当初始化页面 , 解析指令渲染页面前 , 对不同指令模板的所有属性分别添加Watcher

并在  Observer    中 Object.defineProperty 的get方法中往收集依赖器中存放Wacther

2.何时往Dep中添加观察者 , 并能获取到数据变化的属性上绑定的Wacther?

通过   Dep.target = this 来获取 Wacther 并在数据变化后对之前的Wacther进行销毁  Dep.target = null

避免不断添加Wacther , 不易维护

 

  结合   Vue源码解析--实现一个指令解析器 Compile 基础上在解析指令渲染视图时添加观察者 并实现Proxy代理  和 input  视图驱动数据

MVue.js

const compileUtil = {
    getVal(expr, vm) {
        return expr.split('.').reduce((data, currentVal) => {
            return data[currentVal]
        }, vm.$data)
    },
    setVal(expr, vm, inputVal) { //对于嵌套数据用不了
        return expr.split('.').reduce((data, currentVal) => {
            data[currentVal] = inputVal
        }, vm.$data)
    },
    getContentVal(expr, vm) {
        return expr.replace(/{{(.+?)}}/g, (...args) => {
            return this.getVal(args[1], vm);
        })
    },
    text(node, expr, vm) {
        let value;
        if (expr.indexOf('{{') !== -1) {
            value = expr.replace(/{{(.+?)}}/g, (...args) => {
                new Watcher(vm, args[1], (newVal) => { //在解析指令,初始化视图,同时对数据进行监听
                    this.updater.textUpdater(node, this.getContentVal(expr, vm))
                })
                return this.getVal(args[1], vm);
            })
        } else {
            value = this.getVal(expr, vm);
        }
        this.updater.textUpdater(node, value)
    },
    html(node, expr, vm) {
        const value = this.getVal(expr, vm);
        new Watcher(vm, expr, (newVal) => { //在解析指令,初始化视图,同时对数据进行监听
            this.updater.htmlUpdater(node, newVal)
        })
        this.updater.htmlUpdater(node, value)
    },
    model(node, expr, vm) {
        const value = this.getVal(expr, vm);
        //绑定更新函数  数据驱动视图
        new Watcher(vm, expr, (newVal) => { //在解析指令,初始化视图,同时对数据进行监听
            this.updater.modelUpdater(node, newVal)
        })

        //视图=>数据 =>视图
        node.addEventListener('input', (e) => {
            //设置值
            this.setVal(expr, vm, e.target.value);
        })

        this.updater.modelUpdater(node, value)
    },
    on(node, expr, vm, eventName) {
        let fn = vm.$options.methods && vm.$options.methods[expr];
        node.addEventListener(eventName, fn.bind(vm), false);
    },
    updater: {
        textUpdater(node, value) {
            node.textContent = value;
        },
        htmlUpdater(node, value) {
            node.innerHTML = value;
        },
        modelUpdater(node, value) {
            node.value = value;
        }
    }
}
class Complie {
    constructor(el, vm) {
        this.el = this.isElementNode(el) ? el : document.querySelector(el)
        this.vm = vm;
        //1.获取文档碎片对象 ,放入内存中编译,减少回流和重绘
        const fragment = this.node2Fragment(this.el);
        //   2.header编译模板
        this.compile(fragment)
        //3.追加字节点到根元素
        this.el.appendChild(fragment)
    }
    compile(fragment) {
        //1.获取每一个子节点
        const childNodes = fragment.childNodes;
        [...childNodes].forEach(child => {
            if (this.isElementNode(child)) {
                //是元素节点
                // console.log(child)
                //编译元素节点
                this.compileElement(child)
            } else {
                //是文本节点

                // 编译文本节点
                this.compileText(child)
            }
            if (child.childNodes && child.childNodes.length > 0) {
                this.compile(child)
            }
        })
    }
    compileElement(node) {
        const attributes = node.attributes;
        [...attributes].forEach(attr => {
            const {
                name,
                value
            } = attr;

            if (this.isDirective(name)) { //是一个指令 v-model v-text v-on:click 
                const [, direction] = name.split('-'); //text   on:click
                const [dirName, eventName] = direction.split(':'); //分割on:click text  html on click
                //dirName== html || text || no 
                //compileUtil[dirName]需要在compileUtil中分别找到解析html text no 的方法
                //  传入 node value 需要知道哪个节点的哪个值 
                //传入vm 需要在拿到 data中value 对应的值
                //eventName 如果有事件 传入事件
                // console.log(node,value,this.vm,eventName)
                compileUtil[dirName](node, value, this.vm, eventName) //策略模式  数据驱动视图
                //删除有指令的标签上的属性
                node.removeAttribute('v-' + direction)
            } else if (this.isEventName(name)) { //解析@符操作
                let [, eventName] = name.split('@');
                compileUtil['on'](node, value, this.vm, eventName) //策略模式  数据驱动视图          
            }
        })
    }
    compileText(node) {
        const content = node.textContent;
        if (/{{(.+?)}}/.test(content)) {
            compileUtil['text'](node, content, this.vm) //策略模式  数据驱动视图            
        }
    }
    node2Fragment(node) {
        //创建文档碎片
        const f = document.createDocumentFragment();
        let firstChild;
        while (firstChild = node.firstChild) {
            f.appendChild(firstChild)
        }
        return f;
    }
    isElementNode(el) {
        return el.nodeType === 1
    }
    isDirective(attrName) { //判断是否已v-开头
        return attrName.startsWith('v-')
    }
    isEventName(attrName) {
        return attrName.startsWith('@')
    }
}

class MVue {
    constructor(options) {
        this.$data = options.data;
        this.$el = options.el;
        this.$options = options;
        if (this.$el) {
            //实现观察者
            new Observer(this.$data)
            //实现指令解析器
            new Complie(this.$el, this);
            //Proxy代理
            this.proxyData(this.$data)
        }
    }
    proxyData(data) {
        for (const key in data) {
            Object.defineProperty(this, key, {
                get() {
                    return data[key];
                },
                set() {
                    data[key] = newVal;
                }
            })
        }
    }
}
原文地址:https://www.cnblogs.com/wxyblog/p/13540813.html