• 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;
                    }
                })
            }
        }
    }
  • 相关阅读:
    Jenkins解决Host key verification failed
    jenkins+gitlab发布maven项目
    gitlab升级、汉化、修改root密码
    jenkins发布普通项目、配置自动上线自动部署
    Jenkins安装配置
    awk在企业中最常用的语句
    OpenLDAP给我的启发
    三观很正的一些话
    一次完整的http请求过程以及网络I/O模型select、epoll
    Nginx三种模式的虚拟主机(附Apache基于域名的虚拟主机)
  • 原文地址:https://www.cnblogs.com/wxyblog/p/13540813.html
Copyright © 2020-2023  润新知