Vue学习之数据视图驱动Watcher类实现

Vue学习之数据视图驱动Watcher类实现

之前已经监听了数据变化,现在要实现的时在数据初始化时增加对数据的观察,并且在数据变化时,通过数据变化来修改视图。

新建脚本文件

文件名Watcher.js

在index.html中引入<script src="Watcher.js"></script>

class Watcher {
    /**
     * 
     * @param {*} vm Vue实例
     * @param {*} exp data数据key值
     * @param {*} cb 回调函数 用以修改视图
     */
    constructor(vm, exp, cb) {
        this.vm = vm;
        this.exp = exp;
        this.cb = cb;
        this.preValue = this.getInitValue();
    }
    getInitValue() {
        // 添加Watcher时 获取初始值
        const preValue = compileUtil.getValue(this.exp, this.vm);

        return preValue;
    }
    update() {
        // 更新时检查当前值是否有变化 有变化则更新数值
        const newValue = compileUtil.getValue(this.exp, this.vm);
        if (newValue !== this.oldValue) {
            this.cb(newValue)
        }
    }
}

watcher类有两个方法,getInitValue在添加watcher监听时初始化要监听的值,update方法在数值变化时调用回调函数更新视图。

创建Dep类用来绑定Watcher类和Observer类,相互关联。

class Dep {
    constructor() {
        // 初始化数组存放watcher
        this.subs = []
    }
    // 收集watcher
    addSub(watcher) {
        this.subs.push(watcher)
    }
    // 通知watcher 更新
    notify() {
        console.log("watcher:", this.subs);
        this.subs.forEach(e => {
            e.update()
        })
    }
}

Dep类有两个方法,addSub用以收集watcher实例,notify是在收到数据更新时通知watcher更新。

接下来需要关联observer和在页面初始化时添加watcher监听。

在MyVue.js中,在compileUtil中编译值时初始化watcher

getContentValue(exp, vm) {
    return exp.replace(/\{\{(.+?)\}\}/g, (...args) => {
        return this.getValue(args[1], vm)
    })
},
text(node, exp, vm) {
    let value;
    if (exp.indexOf("{{") !== -1) {
        // 对{{person.name}}进行替换
        value = exp.replace(/\{\{(.+?)\}\}/g, (...args) => {
            // 添加watcher
            new Watcher(vm, args[1], (newValue) => {
                this.updater.textUpdater(node, this.getContentValue(exp, vm));
            })
            // 获取到要替换的表达式 args中第二个元素即为需要的元素
            // console.log(args);
            return this.getValue(args[1], vm)
        })
    } else {
        // 获取指令表达式的值
        value = this.getValue(exp, vm);
        new Watcher(vm, exp, (newValue) => {
            this.updater.textUpdater(node, newValue);
        })
    }
    // 更新
    this.updater.textUpdater(node, value);
},

在处理文本时,因可能存在多个文本指令,因此在监听时,每一个值变化要更新整条文本。对html,model,bind指令添加watcher方法类似,如下:

html(node, exp, vm) {
    let value = this.getValue(exp, vm);
    new Watcher(vm, exp, (newValue) => {
        this.updater.htmlUpdater(node, newValue);
    })
    this.updater.htmlUpdater(node, value);
},
model(node, exp, vm) {
    let value = this.getValue(exp, vm);
    new Watcher(vm, exp, (newValue) => {
        this.updater.modelUpdater(node, newValue);
    })
    this.updater.modelUpdater(node, value);
},
bind(node, exp, vm, attrName) {
    let value = this.getValue(exp, vm);
    new Watcher(vm, exp, (newValue) => {
        this.updater.bindUpdater(node, attrName, newValue);
    })
    this.updater.bindUpdater(node, attrName, value);
},

在初始化时获取值时,构造watcher需要将每个watcher通过Dep绑定到对应的observer上,但只有在watcher初始化时才需要添加到Dep中的数组中,因此在Watcher初始化时需要标记。通过在getInitValue方法中添加全局标签实现。

getInitValue() {
//------------关键代码--------------
    Dep.target = this;
    // 添加Watcher时 获取初始值
    const preValue = compileUtil.getValue(this.exp, this.vm);
    Dep.target = null;
//------------关键代码--------------  
    return preValue;
}

在Observer.js中,获取数据时识别是否为初始化Watcher,如果是则将对应Watcher添加到Dep数组中。同时,在数值发生变化时通知dep中对应的watcher更新视图。

defineReactive(data, key, value) {
    const self = this;
    // 递归遍历 直到value不再为object
    self.observer(value);
    const dep = new Dep();
    // 劫持并监听所有属性 利用Object.defineProperty重新定义get和set方法
    Object.defineProperty(data, key, {
        enumerable: true,
        configurable: false,
        get() {
            //--------关键代码----------------
            // 如果是初始化Watcher 则添加到dep数组监听
            Dep.target && dep.addSub(Dep.target);
            //--------关键代码----------------
            return value;
        },
        set(newVal) {
            // 注意在修改对象值时,要对新值添加监听
            self.observer(newVal)
            if (value !== newVal) {
                console.log("监听到数据变化:" + key + " 由 " + value + " 变成 " + newVal);
                value = newVal;
            }
            //--------关键代码----------------
            dep.notify()//--------关键代码----------------
        }
    })
}

在Observer.js中,修改defineReactive方法,添加Dep实例,添加在初始化调用get方法时将watcher加入Dep.

>vm.$data.msg = "我哈哈哈哈哈222文档"           Observer.js:42 
监听到数据变化:msg 由 我哈哈哈哈哈222 变成 我哈哈哈哈哈222文档
<"我哈哈哈哈哈222文档"
>vm.$data.htmlStr = '4444444'                Observer.js:42 
监听到数据变化:htmlStr 由 <h4>还喜欢和大家一起学习。</h4> 变成 4444444
<"4444444"
>vm.$data.person.name = "222222"              Observer.js:42 
监听到数据变化:name 由 小明 变成 222222           MyVue.js:30
大家好!我是222222,今年19岁。
<"222222"
>vm.$data.person.age = "222222"               Observer.js:42 
监听到数据变化:age 由 19 变成 222222             MyVue.js:30 
大家好!我是222222,今年222222岁。
<"222222"
>vm.$data.person.fav = "2233333333333"        Observer.js:42 
监听到数据变化:fav 由 LOL 变成 2233333333333     MyVue.js:30 
我最喜欢的游戏是2233333333333
<"2233333333333"

至此,实现了Vue由视图驱动数据变化并且更新视图,接下来将实现model数据的双向绑定和对vm.$data代理到vm实例上。

demo地址:Demo

blog:我的博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值