• Vue实现数据双向绑定代码


    首先推荐一个b站视频,时长是30分钟,需要耐心多看几遍:
    蛋老师

    数据双向绑定=数据劫持+订阅发布者模式
    总:创建一个Vue类,构造器中传入需要监听的数据,调用模板解析器
    1.数据劫持:监听实例的数据
    对传入数据循环递归实现数据劫持,在get中添加订阅者,返回value
    在set中对订阅者进行通知
    ⚠️:Object.definePropert(操作对象,操作属性,传入一个对象并在对象里实现监听)
    2.数据应用到页面-模板解析器
    获取页面元素-放入临时内存区域-应用vue实例-渲染页面
    将{{name}}对应的属性值用正则解析出来
    2-1:操作节点类型为3的文本节点,使用正则表达式修改插值表达式的内容,在修改内容时创建订阅者实例
    2-2:操作节点类型为1的使用v-model输入框节点,创建订阅者实例,实现数据改变视图,对节点使用addEventListener实现输入框值回显到页面

    3.上面中还用到关于订阅者的两个类,
    一个是依赖,用于收集通知订阅者,一个是用进行数据的更新

    <!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">
        <title>mvvm数据双向绑定</title>
    </head>
    <body>
        <div id="app">
            <span>数据:{{name}}</span>
            <input type="text" v-model="name">
            <span>更多:{{more.like}}</span>
            <input type="text" v-model="more.like">
        </div>
        <script src="./v-model.js"></script>
        <script>
            const vm = new Vue({
                el:'#app',
                data:{
                    name:'lisa',
                    more:{
                        like:'dance'
                    }
                }
            }) 
            console.log(vm)
        </script>
    </body>
    </html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    //数据双向绑定=数据劫持+发布订阅者模式
    class Vue{
        constructor(obj_instance){
            this.$data=obj_instance.data
            Observer(this.$data) //传入需要监听的数据
            //在调用构造vue实例的时候就调用这个模板解析器,this是这个实例
            Compile(obj_instance.el,this)
        }
    }
    
    //数据劫持 --监听实例里面的数据
    function Observer(data_instance){
        //递归出口
        if(!data_instance || typeof data_instance !=='object') return;
        //new一个实例
        const dependency = new Dependency()
        Object.keys(data_instance).forEach(key =>{
            let value = data_instance[key];
            Observer(value)  //子属性数据劫持
            // defineProperty 参数操作对象,操作属性,传入一个对象并在对象里实现监听
            Object.defineProperty(data_instance,key,{
                //会被循环中列出
                enumerable:true,
                //可以被删除修改
                configurable:true,
                //访问属性值时触发
                get(){
                    console.log(`访问了属性:${key} 值:->${value}`)
                    //订阅者加入依赖实例的数组
                    Dependency.temp && dependency.addSub(Dependency.temp)
                    console.log("Dependency.temp",Dependency.temp)
                    return value
                },
                //修改属性值时触发
                set(newValue){
                    console.log(`属性 ${key} 的值 ${value}修改为-> ${newValue}`)
                    value=newValue
                    Observer(newValue) //将value改为对象时,进行数据劫持
                    dependency.notify()
                }
            })
        })
    }
    //2.数据应用到页面
    //获取页面元素-放入临时内存区域-应用vue数据--渲染页面
    //html模板解析---替换DOM内
    //两个参数,第一个元素,第二个实例,将vue数据解析到页面上
    function Compile(element,vm){
        //获取页面元素
        vm.$el=document.querySelector(element);
        //将子节点放入临时内存区域
        const fragement =document.createDocumentFragment();
        let child
        while(child=vm.$el.firstChild){
            fragement.append(child)
        }
        fragement_compile(fragement)
        //修改fragement里面的内容,替换文档碎片内容,
        //操作节点类型为3的文本节点
        function fragement_compile(node){
        //修改插值表达式内容,使用正则表达式
            const pattern = /\{\{s*(\S+)\s*\}\}/
            if(node.nodeType === 3){
                const xxx= node.nodeValue
                const result_regex=pattern.exec(node.nodeValue)
                if(result_regex){
                    const arr=result_regex[1].split('.');
                    const value = arr.reduce(
                        (total,cur)=>total[cur],vm.$data
                    )
                    node.nodeValue=xxx.replace(pattern,value)  
                    //创建订阅者实例,这个时候修改内容(替换文档碎片时) 
                    new Watcher(vm,result_regex[1],newValue=>{
                        node.nodeValue=xxx.replace(pattern,newValue) 
                    })     
                }
                return
            }
            if(node.nodeType === 1 && node.nodeName === 'INPUT'){
                const attr = Array.from(node.attributes) 
                attr.forEach(i=>{
                    if(i.nodeName ==='v-model'){
                        //对应属性名为i.nodeValue
                        const value =i.nodeValue.split('.').reduce(
                            (total,cur) =>total[cur],vm.$data
                        )
                        node.value=value
                        //创建订阅者实例,这个时候修改内容(替换文档碎片时)
                        //数据改变视图 
                        new Watcher(vm,i.nodeValue,newValue=>{
                            node.value=newValue
                        })
                        node.addEventListener('input',e=>{
                            //['more','like']
                            const arr1 = i.nodeValue.split('.')
                            //['more']
                            const arr2 =arr1.slice(0,arr1.length-1)
                            //vm.$data.more
                            const final =arr2.reduce(
                                (total,cur) => total[cur],vm.$data
                            )
                            //vm.$data.more['like']=e.target.value
                            final[arr1[arr1.length-1]]=e.target.value
                        })
                    }
                })
            }
            node.childNodes.forEach(child=>fragement_compile(child))
        }
        //将文档碎片应用到页面
        vm.$el.append(fragement)
    }
    
    //3.数据变动进行及时更新:发布者-订阅者模式(发布者有内容更新就通知到订阅者)
    //依赖---收集和通知订阅者
    class Dependency{
        constructor(){
            //用来存放订阅者
            this.subscribers=[]
        }
        addSub(sub){
            this.subscribers.push(sub)
        }
        notify(){
            this.subscribers.forEach(sub=>sub.update())
        }
        
    }
    //订阅者
    class Watcher{
        //更新哪些数据,需要用到vue上的实例
        // callback 记录如何更新文本内容
        constructor(vm,key,callback){
            //参数都负值给实例
            this.vm=vm
            this.key=key
            this.callback=callback
            //临时属性,触发getter
            //可以在触发getter的时候来添加订阅者到订阅者数组里面
            Dependency.temp=this
            key.split('.').reduce((total,cur)=>total[cur],vm.$data)
            // 防止订阅者多次加入依赖实例的数组
            Dependency.temp=null
        }
        //更新适宜自己的内容,发布者通知订阅者可以更新了
        update(){
            //获取属性值
            const value = this.key.split('.').reduce(
                (total,cur) => total[cur],this.vm.$data
            )
            this.callback(value)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
  • 相关阅读:
    计算机网络——作业4.3
    数据可视化在商业领域有哪些重要性?
    API接口名称(item_search - 京东按关键字搜索商品)[item_search,item_get,item_search_shop等]
    【Qt控件之QRadioButton】使用及技巧
    PAT 1025 反转链表
    读懂Json文件[妈妈再也不用担心我不读懂了]
    Android R窗口Window的requestlayout过程大揭秘
    高速路自动驾驶功能HWP功能定义
    rabbitmq默认交换机锁绑定的routingkey-待研究
    java 开发第一个app工程
  • 原文地址:https://blog.csdn.net/qq_44741441/article/details/126609311