美文网首页
Vue原理(2)数据劫持

Vue原理(2)数据劫持

作者: 我喜欢吃辣椒 | 来源:发表于2020-06-30 14:29 被阅读0次

在上篇文章的基础上,Vue核心,数据劫持。创建observer.js文件,将上篇文章的代码复制过来,文件结构如下


文件结构.png

在Vue类中将数据data转化为object.definProperty来定义

class Vue {
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        //判断根元素是否存在,存在编译模板
        if (this.$el) {
            //将数据全部转化为object.defineProperty来定义
            new Observer(this.$data) //新增
            new Compiler(this.$el, this);//编译
        }
    }
}

数据劫持类

class Observer {
    constructor(data) {
        this.observer(data)
    }
    observer(data) {
        if (data && typeof data == 'object') {
            //判断data合法性,如果是对象才观察,如果是对象
            for (let key in data) {
                this.defineReactive(data, key, data[key])
            }
        }
    }
    defineReactive(obj, key, value) {
        this.observer(value) //如果值也是对象,递归劫持
        Object.defineProperty(obj, key, {
            get() {
                return value
            },
            set:(newValue)=>{
                if (newValue != value) {
                    this.observer(newValue) //赋值的也是对象的话 ,也要劫持
                    value = newValue
                }
            }
        })
    }
}

数据劫持之后,进行改变更新视图,采用发布订阅,首先写一个观察者

class Watcher {
    constructor(vm, expr, cb) {
        this.vm = vm
        this.expr = expr
        this.cb = cb
        //默认存放一个老值,当值变化时,对比,变化则更新
        this.oldValue = this.get()
    }
    get() {
        let value = CompilerUtil.getValue(this.vm, this.expr)
        return value
    }
    update() { //更新操作
        let newValue = CompilerUtil.getValue(this.vm, this.expr)
        if (newValue != this.oldValue) {
            this.cb(newValue) //回调函数
        }
    }
}

写发布订阅类

//订阅发布
class Dep {
    constructor() {
        this.subs = [] //存放所有的观察者
    }
    //订阅
    addSub(watcher) {//添加所有的watcher
        this.subs.push(watcher)
    }
    //发布
    notify() {
        this.subs.forEach(watcher => {
            watcher.update()
        })
    }
}

在编译元素和文本时候加入观察者,在编译工具中的 model 和 text内加入,示例代码,具体请移步git

  getContentValue(vm,expr){
        //遍历表达式,将内容重新替换成一个完整的内容
        return expr.replace(/\{\{(.+?)\}\}/g,(...args)=>{
            return this.getValue(vm,args[1])
        })
    },
 model(node, expr, vm) {
        let value = this.getValue(vm, expr)
        let fn = this.updater['modelUpdater']
        new Watcher(vm,expr,(newValue)=>{ //给输入框加上观察者,数据更新触发这个方法,给输入框赋新值,新增
            fn(node,newValue)
        })
        fn(node, value)
    },
text(node, expr, vm) {
        let fn = this.updater['textUpdater']
        let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
            //给表达式的每个变量都加上观察者
            new Watcher(vm,args[1],()=>{
                fn(node,this.getContentValue(vm,expr)) //返回全的字符串
            })
            return this.getValue(vm, args[1])
          
        })
        
        fn(node, content)
    },

最最最重点,new Watcher之后,我们如何把它放进订阅的subs数组。js是单线程,
每次new Watcher,都会调用获取数据,我们的数据用Observer类劫持,所以在劫持的时候,加入发布订阅功能

defineReactive(obj, key, value) {
        this.observer(value) //如果值也是对象,递归劫持
        //重点
        let dep = new Dep() //给每个属性都加入发布订阅的功能  
        Object.defineProperty(obj, key, {
            get() {
                return value
            },
            set: (newValue) => {
                if (newValue != value) {
                    this.observer(newValue) //赋值的也是对象的话 ,也要劫持
                    value = newValue
                }
            }
        })
    }

此时watcher方法如何和发布订阅联系起来,全局有Dep类,new Watcher是get方法内,将new Watcher放在全局的Dep上,Watcher内的get方法如下

get() { 
        Dep.target = this  //watcher先把自己放在this上
        //取值,把观察者和数据结合起来
        let value = CompilerUtil.getValue(this.vm, this.expr)
        return value
    }

Dep.target上有了Watcher,我们在数据劫持时,将new Watcher放入subs数组

 defineReactive(obj, key, value) {
        this.observer(value) //如果值也是对象,递归劫持
        let dep = new Dep() //给每个属性都加入发布订阅的功能
        Object.defineProperty(obj, key, {
            get() {
                //创建watcher时取到对应的内容,并且把watcher放到全局上。
                Dep.target && dep.addSub(Dep.target)
                return value
            },
            set: (newValue) => {
                if (newValue != value) {
                    this.observer(newValue) //赋值的也是对象的话 ,也要劫持
                    value = newValue
                }
            }
        })
    }

将Dep.target清空
在Watcher的get方法上加入

 get() { 
        Dep.target = this //watcher先把自己放在this上
        //取值,把观察者和数据结合起来
        let value = CompilerUtil.getValue(this.vm, this.expr)
        //此时加入
        Dep.target = null 
        return value
    }

最后,在数据更改的时候调用发布,即数据劫持的set方法

 defineReactive(obj, key, value) {
        this.observer(value) //如果值也是对象,递归劫持
        let dep = new Dep() //给每个属性都加入发布订阅的功能
        Object.defineProperty(obj, key, {
            get() {
                //创建watcher时取到对应的内容,并且把watcher放到全局上。
                Dep.target && dep.subs.push(Dep.target)
                return value
            },
            set: (newValue) => {
                if (newValue != value) {
                    this.observer(newValue) //赋值的也是对象的话 ,也要劫持
                    value = newValue
                    //此时加入
                    dep.notify()
                }
            }
        })
    }

完成发布订阅,此时在浏览器控制台输入 vm.$data.author.name = "wmm",即可看到变化
v-model现在无法实现输入数据改变视图,CompilerUtil 编译类加入

setValue(vm, expr, val) { //输入值,试图更新
        expr.split('.').reduce((data, current, index, arr) => {
            if (index == arr.length - 1) {
                data[current] = val
            }
            return data[current]
        }, vm.$data)
    },
// 解析v-model
    model(node, expr, vm) {
        let value = this.getValue(vm, expr)
        let fn = this.updater['modelUpdater']
        new Watcher(vm, expr, (newValue) => { //给输入框加上观察者,数据更新触发这个方法,给输入框赋新值
            fn(node, newValue)
        })
        node.addEventListener('input', (e) => {
            let val = e.target.value
            this.setValue(vm, expr,val)
        })
        fn(node, value)
    },

此时取值方式为vm.$data.xx,期望的方式应该为vm.xxx,加上代理
在Vue类上加入代理

class Vue {
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        //判断根元素是否存在,存在编译模板
        if (this.$el) {
            //将数据全部转化为object.defineProperty来定义
            new Observer(this.$data) //新增
            //把数据获取操作,vm上的取值操作,都代理到vm.$data
            this.proxyVm(this.$data) //代理 此时加入
            new Compiler(this.$el, this);//编译
        }
    }
    proxyVm(data){
        for(let key in data){
            Object.defineProperty(this,key,{
                get(){
                    return data[key]//进行转化
                },
                set(newValue){
                    data[key] = newValue
                }
            })
        }
    }
}

此时只需要用vm.xxx即可获取数据
git地址:https://github.com/wangxunahe1996/Vue-mvvm

相关文章

  • Vue2.x 的双向绑定原理及实现

    Vue 数据双向绑定原理 Vue 是利用的 Object.defineProperty()方法进行的数据劫持,利用...

  • Vue原理(2)数据劫持

    在上篇文章的基础上,Vue核心,数据劫持。创建observer.js文件,将上篇文章的代码复制过来,文件结构如下 ...

  • vue数据绑定原理

    实现原理:vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的.1)数据劫持、vue是通过Obje...

  • vue面试知识点

    vue 数据双向绑定原理 vue实现数据双向绑定原理主要是:采用数据劫持结合发布订阅设计模式的方式,通过对data...

  • vue实现双向绑定原理

    原理 vue数据双向绑定通过‘数据劫持’ + 订阅发布模式实现 数据劫持 指的是在访问或者修改对象的某个属性时,通...

  • vue2.x响应式原理

    vue3.0发布了,回顾一波2.x原理 vue 2.x 响应式原理 主要做了这么几件事:数据劫持、收集依赖、派发更...

  • Vue MVVM 原理实现

    核心原理 MVVM 双向数据绑定, 数据驱动视图 Vue 实现 MVVM 采用 数据劫持 + 发布订阅模式 : ...

  • vue2和vue3的响应式原理

    一、vue2响应式 1.实现原理 (1)对象类型 通过 对属性的读取、修改进行拦截(数据劫持)。 (2)数据类型 ...

  • vue面试常被问到的问题整理

    vue面试常被问到的问题整理 1、Vue的双向数据绑定原理是什么? 答 : vue是采用数据劫持,并且使用发布-订...

  • vue 数据劫持原理

网友评论

      本文标题:Vue原理(2)数据劫持

      本文链接:https://www.haomeiwen.com/subject/dnntqktx.html