Vue数据双向绑定原理

index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script src="./vue.js"></script>
    <title>Document</title>
  </head>
  <body>
      <div id="app">
        <h3>姓名是:{{ name }}</h3>
        <h3>年龄是:{{ info.age }}</h3>
        <input v-model="name" type="text"></div>
      
    
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          name: "Ada",
          info: {
            age: "18",
            gender: "女",
          },
        },
      });
      // console.log(vm.$data);
    </script>
  </body>
</html>

vue.js:

class Vue{
    constructor(options){
        this.$data = options.data

        //调用数据劫持的方法
        Observe(this.$data)

        //属性代理
        Object.keys(this.$data).forEach(key=>{
            Object.defineProperty(this,key,{
                enumerable:true,
                configurable:true,
                get(){
                    return this.$data[key]
                },
                set(newVal){
                    this.$data[key] = newVal
                }
            })
        })

        //调用模板编译的函数
        Compile(options.el,this)
    }
}

//定义一个数据劫持的方法
function Observe(obj){
    //递归的终止条件
    if(!obj||typeof obj!=='object') return
    const dep = new Dep()

    Object.keys(obj).forEach(key=>{
        let value = obj[key]
        

        //将value这个子节点进行递归
        Observe(value)
        //给当前key对应的属性添加setter和getter
        Object.defineProperty(obj,key,{
            enumerable:true,
            configurable:true,
            get(){
                Dep.target && dep.addSub(Dep.target)
                console.log(`有人获取了${key}的值`)
                return value
            },
            set(newVal){
                value = newVal
                Observe(value)
                //通知每一个订阅者更新自己的文本
                dep.notify()
            }
        })
    })
}

//对html结构进行模板编译的方法
function Compile(el,vm){
    //获取el对应的dom元素
    vm.$el = document.querySelector(el)
    
    // 创建文档碎片提高DOM操作性能
    //在内存中操作DOM元素再渲染到页面上,避免重绘重排,提高DOM渲染的性能
    const fragment = document.createDocumentFragment()
    while(childNode = vm.$el.firstChild){
        fragment.appendChild(childNode)
    }

    //进行模板编译
    replace(fragment)

    vm.$el.appendChild(fragment)

    //负责对DOM模板进行编译的方法
    function replace(node){
        //定义匹配插值表达式的正则
        const resMustache = /\{\{\s*(\S+)\s*\}\}/

        //证明当前的 node 节点是一个文本子节点,需要进行正则的替换
        if(node.nodeType === 3){
         //文本子节点也是一个DOM对象,如果要获取文本子节点字符串的内容,需要调用textContent属性获取   
         const text = node.textContent
        
         //进行字符串的正则匹配与提取
         const execResult = resMustache.exec(text)


         if(execResult){
             const value = execResult[1].split('.').reduce((newObj,k)=> newObj[k],vm)
             node.textContent = text.replace(resMustache, value)

             //创建watcher类的实例
             new Watcher(vm,execResult[1],(newVal)=>{
                node.textContent = text.replace(resMustache, newVal)
                console.log(node.textContent)
             })
         }

         //终止递归的条件
         return
        }

        //判断当前的属性是否为input输入框
        if(node.nodeType === 1 && node.tagName.toUpperCase() === 'INPUT'){
            //得到当前元素所有的属性节点
            const attrs = Array.from(node.attributes) 
            const findResult = attrs.find((x) => x.name === 'v-model')
            // console.dir(findResult)
            if(findResult){
                //获取当前v-model属性的值
                const expStr = findResult.value
                const value = expStr.split('.').reduce((newVal,key)=>newVal[key],vm)
                console.log(value)
                node.value = value

                //创建Watcher的实例
                new Watcher(vm, expStr, (newVal)=>{
                    node.value = newVal
                })

                //监听文本框的input输入事件,拿到文本框最新的值,把最新的值更新到vm上
                node.addEventListener('input',(e)=>{
                    const keyArr =  expStr.split('.')
                    const obj = keyArr.slice(0,keyArr.length - 1).reduce((newVal,k)=>newVal[k],vm)
                    obj[keyArr[keyArr.length - 1]] = e.target.value  
                })
            }
        }

        //证明不是文本节点,可能是一个DOM元素,需要进行递归处理
        node.childNodes.forEach(child=>replace(child))
    }
    // replace(childNode)
}

//依赖收集的类/收集watcher订阅者的类
class Dep{
    constructor(){
        //所有的watcher都要存到这个数组中
        this.sub = []
    }

    //向sub数组中添加watcher的方法
    addSub(watcher){
        this.sub.push(watcher)
    }

    //负责通知每个watcher的的方法
    notify(){
        this.sub.forEach(watcher => watcher.update())
    }
}

class Watcher{
    //cb回调函数中,记录着当前Watcher如何更新自己的文本内容
    //仅仅知道如何更新自己还是不行,必须拿到最新的数据
    //需要在new Watcher期间,把vm也传进来,因为vm中保存着最新的数据
    //除此之外,还需要知道在众多的数据中,哪个数据才是当前自己需要的数据
    // 因此,必须在new Watcher期间,指定watcher对应数据的名字
    constructor(vm,key,cb){
        this.vm = vm,
        this.key = key,
        this.cb = cb

        //将创建的Watcher实例存到Dep实例的subs数组中
        Dep.target = this
        //这里会调用上面数据劫持的getter,将this传进去
        key.split('.').reduce((newObj,k) => newObj[k],vm)
        Dep.target = null
    }

    //watcher的实例需要有update函数,从而让发布者通知我们进行更新 
    update(){
        const value = this.key.split('.').reduce((newObj,k) => newObj[k],this.vm)
        this.cb(value)
        console.log(this.key)
        // this.cb
    }
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值