Vue响应原理理解

Vue响应原理理解

Vue采用的数据劫持+发布订阅模式实现数据响应

原理流程图:

细节图
在这里插入图片描述
代码操作过程的响应流程:

当编译类编译时,需读取数据,此时调用数据劫持中的get方法,后生成了一个Watcher实例,实例执行getOldVal方法,将实例赋给Dep.target,进入get方法,将实例添加进入观察者,当数据跟新时,执行Watcher的回调函数,执行跟新函数。

// 代码
const CompileUtils={
    'text':function (node,expr,vm) {
        let value;
        if(expr.indexOf('{{')>-1){
            // {{person.name}} -- {{person.age}}
            value = expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
                new Watcher(args[1],vm,()=>{
                    this.updater.updateText(node,this.getContent(expr,vm));
                })
               return this.getVal(args[1],vm)
            })
        }else{
            new Watcher(expr,vm,(newVal)=>{
                this.updater.updateText(node,newVal);
            })
            value = this.getVal(expr,vm);
        }
        this.updater.updateText(node,value);
    },
    'html':function (node,expr,vm) {
        const value = this.getVal(expr,vm);
        // 跟新中new一个Watcher
        new Watcher(expr,vm,(newVal)=>{
            this.updater.updateHtml(node,newVal);
        })
        this.updater.updateHtml(node,value);
    },
    'model':function (node,expr,vm) {
        const value = this.getVal(expr,vm);
        new Watcher(expr,vm,(newVal)=>{
            this.updater.updateModel(node,newVal);
        })
        // 数据->视图
        this.updater.updateModel(node,value);
        // 视图->数据->视图
        node.addEventListener('input',(e)=> {
            this.setVal(expr,vm,e.target.value);
        })
    },
    'on':function (node,expr,vm,eventName) {
        const fn = vm.$methods[expr];
        node.addEventListener(eventName,fn.bind(vm));
    },
    'bind':function (node,expr,vm,attrName) {
        const value = this.getVal(expr,vm);
        node.setAttribute(attrName,value);
    },
    getContent(expr,vm){
        return expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
            return this.getVal(args[1],vm);
        })
    },
    getVal(expr,vm){
        return expr.split('.').reduce((init,current)=>{
            return init[current];
        },vm.$data)
    },
    setVal(expr,vm,value){
      expr.split('.').reduce((init,current,index,arr)=>{
         if(index === (arr.length-1)){
               init[current] = value;
           }
           return init[current];
       },vm.$data);
    },
    updater:{
        updateHtml:function (node,value) {
            node.innerHTML = value;
        },
        updateText:function (node,value) {
            node.textContent = value;
        },
        updateModel:function (node,value) {
            node.value = value;
        }
    }
}
class Compile {
    constructor(node,vm) {
        this.vm = vm;
        // 把节点放到文档碎片中
        const fragment = this.getFragment(node);
        // 编译文档碎片
        this.compile(fragment);
        // 追加文档碎片到node下
        node.appendChild(fragment);
    }
    compileText(node){
        const value = node.textContent;
        // 用正则筛选{{}}
        if(/\{\{(.+?)\}\}/.test(value)){
            CompileUtils['text'](node,value,this.vm);
        }
    }
    compileElement(node){
        // <h1 v-html v-text v-on:click></h1>
       const attributes = node.attributes;
       [...attributes].forEach(attr=>{
           const {name,value} = attr;
           // 判断是否v-开头的指令
           if(this.isDirective(name)){
               // v | text html on:click bind:src
               const [,directive] = name.split('-');
               const [directName,eventName] = directive.split(':');
               // 传入修改的节点,属性值
               CompileUtils[directName](node,value,this.vm,eventName);
               node.removeAttribute(name);
           }else if(this.isMethod(name)){
                // @开头的
               const [,eventName] = name.split('@');
               CompileUtils['on'](node,value,this.vm,eventName);
               node.removeAttribute(name);
           }
       })
    }
    isMethod(eventName){
        return eventName.startsWith('@');
    }
    isDirective(attrName){
        return attrName.startsWith('v-');
    }
    compile(node){
        // 遍历里面所有的节点
        const childnodes = node.childNodes;
        [...childnodes].forEach(i=>{
            // 判断是文本节点还是元素节点
            if(this.isTextElement(i)){
                // 编译文本节点
                this.compileText(i);
            }else{
                // 编译元素节点
                this.compileElement(i);
            }
            if(i.childNodes && i.childNodes.length){
                this.compile(i);
            }
        })
    }
    isTextElement(node){
        return node.nodeType === 3;
    }
    getFragment(node){
        const fragment2Node = document.createDocumentFragment();
        // 遍历node节点的第一个子元素
        let firstnode;
        while(firstnode = node.firstChild){
            fragment2Node.appendChild(firstnode);
        }
        return fragment2Node;
    }
}
class MVue {
    constructor(options) {
        // 判断el是否节点
        this.$el = this.isElementNode(options.el) ? options.el : document.querySelector(options.el);
        this.$data = options.data;
        this.$methods = options.methods;
        // 如果el存在。
        if(this.$el){
            // 1.观察者类,劫持数据
            new Observer(this.$data)
            // 2.编译类
            new Compile(this.$el,this);
            // 3.代理
            this.proxyData(this.$data);
        }else{
            throw new Error('el元素不存在')
        }
    }
    isElementNode(node){
        return node.nodeType === 1;
    }
    proxyData(data){
        Object.keys(data).forEach(key=>{
            Object.defineProperty(this,key,{
                enumerable:true,
                configurable:false,
                set(v) {
                  data[key] = v;
                },
                get() {
                   return data[key];
                }
            })
        })
    }
}
class Watcher {
    constructor(expr,vm,cb) {
        this.expr = expr;
        this.vm = vm;
        this.cb = cb;
        this.oldVal = this.getOldVal();
    }
    // 获取旧值
    getOldVal(){
       Dep.target = this;
       const oldval = CompileUtils.getVal(this.expr,this.vm);
       Dep.target = null;
       return oldval;
    }
    // 跟新方法
    update(){
        const newVal = CompileUtils.getVal(this.expr,this.vm);
        if(newVal !== this.oldVal){
            // 将值回调回去
            this.cb(newVal);
        }
    }
}

class Dep {
    constructor() {
        // 收集订阅者
        this.subs = [];
    }
    // 添加订阅者方法
    addSub(watcher){
        this.subs.push(watcher);
    }
    // 通知Watcher跟新方法
    notify(){
        this.subs.forEach(w=>w.update())
    }
}

class Observer {
    constructor(data) {
        // 劫持数据
        this.observe(data);
    }
    observe(data){
        // data存在且为对象只遍历对象
        if(data && typeof data ==='object'){
            // 遍历数据
            // for in 遍历包括原型链
            // Object.keys 只包含自身可枚举
            Object.keys(data).forEach(key=>{
                this.defineReactive(data,key,data[key]);
            })
        }
    }
    defineReactive(data,key,value){
        const dep = new Dep();
        this.observe(data[key]);
        Object.defineProperty(data,key,{
            enumerable:true,
            configurable:false,
            get() {
                // 初始化,收集订阅者
                Dep.target && dep.addSub(Dep.target);
                return value;
            },
            set(v) {
                value = v;
                // 通知变化
                dep.notify();
            }
        })
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值