【Vue高级】MVVM实现原理(六)—— 双向数据绑定的实现

在上节代码中加了一个功能

 在模板编译器方法里加了以下代码

//nodeType为1时,是元素节点
            if(node.nodeType === 1){
                let nodeAttrs = node.attributes  //类数组对象
                Array.from(nodeAttrs).forEach((attr)=>{
                    console.log(attr.name)
                    let name = attr.name
                    let exp = attr.value //v-model = 'b'
                    if(name.indexOf('v-') == 0){//v-model
                        node.value = vm[exp]
                    }
                    new Watcher(vm, RegExp.$1, function(newVal){//函数里需要接收一个新的值
                        node.value = vm[exp] //当watcher触发时描绘自动将内容放到输入框里
                    })
                    node.addEventListener('input',function(e){
                        let newVal = e.target.value;
                        vm[exp] = newVal  // this.c = 新值
                    })
                })
            }
function Vue(options = {}) {
    this.$options = options;//将所有属性挂载在options

    var data = this._data = this.$options.data;

    observe(data)

    //观察完之后,this代理了this._data  数据代理
    for (let key in data) {
        Object.defineProperty(this, key, {
            enumerable: true,
            get() {
                return this._data[key];//获取到新值
            },
            set(newVal) {
                this._data[key] = newVal//这里触发观察者【再次执行Observe(data)中的set方法】
            }
        })
    }
    new Compile(options.el, this)
}


//模板编译器
function Compile(el, vm) {
    //el代表替换的范围
    vm.$el = document.querySelector(el);

    /*获取到元素以后,把里面的子元素都拿到,需要移动到内存中操作,所以需要创建文档碎片*/
    //创建一个虚拟的节点对象,或者说,是用来创建文档碎片节点。它可以包含各种类型的节点,在创建之初是空的。
    let fragment = document.createDocumentFragment();

    //把app里面的内容都放到内存中,此时页面上为空白
    while (child = vm.$el.firstChild) {
        fragment.appendChild(child)
    }

    //递归 替换虚拟节点中的 {{}}
    replace(fragment)
    function replace(fragment) {
        Array.from(fragment.childNodes).forEach((node) => {
            //循环每一层
            let text = node.textContent;
            let reg = /\{\{(.*)\}\}/

            //nodeType为3时,是文本内容
            if (node.nodeType === 3 && reg.test(text)) {
                //console.log(RegExp.$1) // a.a vm.b
                let arr = RegExp.$1.split('.') //[a,a]
                let val = vm;
                arr.forEach((k) => {
                    val = val[k]
                })

                new Watcher(vm, RegExp.$1, function(newVal){//函数里需要接收一个新的值
                    node.textContent = text.replace(/\{\{(.*)\}\}/, newVal)
                })

                //替换逻辑
                node.textContent = text.replace(/\{\{(.*)\}\}/, val)
            }
            //nodeType为1时,是元素节点
            if(node.nodeType === 1){
                let nodeAttrs = node.attributes  //类数组对象
                Array.from(nodeAttrs).forEach((attr)=>{
                    console.log(attr.name)
                    let name = attr.name
                    let exp = attr.value //v-model = 'b'
                    if(name.indexOf('v-') == 0){//v-model
                        node.value = vm[exp]
                    }
                    new Watcher(vm, RegExp.$1, function(newVal){//函数里需要接收一个新的值
                        node.value = vm[exp] //当watcher触发时描绘自动将内容放到输入框里
                    })
                    node.addEventListener('input',function(e){
                        let newVal = e.target.value;
                        vm[exp] = newVal  // this.c = 新值
                    })
                })
            }
            if (node.childNodes) {
                replace(node)
            }
        })
    }

    //把内存中的文档碎片放回到页面上,此时页面上的东西显示回来
    vm.$el.appendChild(fragment)

}



//vm.$options
function Observe(data) {//这里写我们的主要逻辑
    let dep = new Dep()
    for (let key in data) {//把data属性通过object.defineProperty的方式定义

        let val = data[key]

        observe(val)//递归 使data的中的每个属性都被Object.defineProperty处理

        Object.defineProperty(data, key, {
            enumerable: true,
            get() {
                
                //把watcher 放入到 订阅者中
                Dep.target && dep.addSub(Dep.target) //[watcher]

                return val;
            },
            set(newVal) {//更改值得时候
                if (newVal === val) {//设置的值和以前是一样的东西
                    return;
                }
                val = newVal;//如果以后在获取值得时候,将刚才设置的值丢回去
                observe(newVal)

                //发布
                dep.notify() // 让所有的watcher的update方法执行
            }
        })
    }
}
function observe(data) {
    if (typeof data !== 'object') {
        return
    }
    return new Observe(data)
}



//发布订阅模式  先有订阅再有发布

//有一个方法,可以订阅一些事件 这些事件会放在数组里 [fn1,fn2,fn3]


function Dep() {
    this.subs = [];
}

//订阅的实现
Dep.prototype.addSub = function (sub) {
    this.subs.push(sub)
}

//发布的实现     绑定的方法,如[fn1,fn2,fn3] 都有一个update属性
Dep.prototype.notify = function () {
    this.subs.forEach(sub => sub.update())
}

//Watcher
function Watcher(vm, exp, fn) {
    this.fn = fn
    this.vm = vm
    this.exp = exp

    //添加到订阅中
    Dep.target = this  //获取当前的 watcher实例  添加到订阅中
    let val = vm
    let arr = exp.split('.')
    arr.forEach((k) => { // this.a.a  回调用 get方法
        val = val[k]
    })
    Dep.target = null
}
Watcher.prototype.update = function () {
    let val = this.vm
    let arr = this.exp.split('.')
    arr.forEach((k) => { // this.a.a  回调用 get方法
        val = val[k]
    })
    this.fn(val) //
}
发布了248 篇原创文章 · 获赞 32 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/qq_37899792/article/details/105560801