双向数据绑定的实现原理(mini-vue)

双向数据绑定的实现原理(mini-vue)

采用数据劫持结合发布&订阅者模式,使用 Object.definePropert 给每个属性添加getter 和 setter ,在数据变动时发布消息个订阅者,触发响应的监听回调

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head>
</head>
<body>
    <div id="app">
        <h3>名字:{{name}}</h3>
        <h3>年龄:{{age}}</h3>
        <input type="text" v-model='age'>
    </div>
    // 这是引入自己写的vue.js 文件
    <script src="./vue.js"></script>
    <script>
        const vm = new Vue({
            el: '#app',
            data: {
                name: 'zs',
                age: 20,
                info: {
                    a: 'a',
                    c: 'c'
                }
            }
        })
    </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.key(obj)    
    Object.keys(obj).forEach(key=>{
       
        let value = obj[key]
        //递归
        Observe(value)
        // 需要为当前的 key 所对应的属性,添加 getter 和 setter
        Object.defineProperty(obj,key,{
              enumerable:true,//是否可配置
              configurable:true,//是否可枚 举
              get(){
                Dep.target && dep.addSub(Dep.target)
                  return value
              },
               set(newVal){
                value=newVal
                Observe(value)
                dep.notify()
              },

        })
    })
}

// 对 html 结构进行模板编译
function Compile(el,vm,) {
    //获取 el 对应的 DOM 元素
   vm.$el = document.querySelector(el)
    
   // 创建文档碎片 提高 DOM 操作性能
   const fragment = document.createDocumentFragment()

   while(childNode = vm.$el.firstChild){

    fragment.appendChild(childNode)
   }

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

   vm.$el.appendChild(fragment)

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

       // 证明当前的 node 节点是一个文本子节点 需要进行正则替换
       if(node.nodeType===3){
           const text = node.textContent
           
        //进行字符串的正则匹配与提取
        const execResult = regMustache.exec(text)
        if(execResult){
           const value =  execResult[1].split('.').reduce((newObj,k)=>newObj[k],vm)
           node.textContent = text.replace(regMustache,value)
           // 在这个时候创建watcher类的实例
           new Watcher(vm,execResult[1],(newVal)=>{
            node.textContent = text.replace(regMustache,newVal)
           
           })
          

        }

        //终止递归的条件
        return
       }
       //判断当前的 node 节点是否为 input 输入框
       if(node.nodeType === 1 && node.tagName.toUpperCase()==='INPUT'){
          //得到当前元素所以属性的伪数组
        const attrs = Array.from(node.attributes)
        const findResult =  attrs.find(x=>x.name==='v-model')
        if(findResult){
            // 获取当前 v-model属性的值 v-model='name.a'
            const expStr = findResult.value
            const value = expStr.split('.').reduce((newObj,k)=>newObj[k],vm)
            node.value =value
            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((newObj,k)=>newObj[k],vm)
               obj[keyArr[keyArr.length -1]] = e.target.value
            })
        }
       }
       
       //证明不是一个文本节点 可能是一个 DOM 元素  需要递归处理
       node.childNodes.forEach(child=>replace(child)) 
   }

}

//依赖收集 Dep   订阅者的类
class Dep{

    constructor(){

        this.subs=[]
    }
    // 向 subs 数组中添加 watcher 方法
    addSub(watcher){
        this.subs.push(watcher)
    }
    //负责通知每个 watcher 的方法
    notify(){
        this.subs.forEach(watcher=>{
            watcher.upData()
        })
    }   
}


class Watcher{
    // cb 回调函数中,记录着当前 watcher 如何更新自己的文本内容
    // 但是,只知道如何更新自己还不行,还必须拿到最新的数据
    // 因此,还需要在 new Wacher 期间,把vm也传进来 
    // 除此之外,还需要知道,在 vm 身上的数据中,哪个数据,才是自己所需要的数据
    constructor(vm,key,cb,){
        this.cb = cb
        this.key = key
        this.vm = vm

        // 下面三行代码,负责把创建的 watcher 实例存到 Dep 实例的subs
        Dep.target = this 
        key.split('.').reduce((newObj,k)=>newObj[k],vm)
        Dep.target = null 
    }
    upData(){
        const value = this.key.split('.').reduce((newObj,k)=>newObj[k],this.vm)
        this.cb(value)
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值