面试题:vue实现双向数据绑定的原理(附源代码)

原文连接: https://blog.csdn.net/jiang7701037/article/details/104419524

此面试题浅层次的解释在:前端面试题:vue 的双向数据绑定原理,v-model的源码

深层次,其实就是问你vue数据绑定的原理:

1、使用Object.defineProperty进行数据劫持,把data对象,computed等里的所有属性进行数据劫持。数据劫持的意思可以看:JavaScript中的Object.defineProperty()函数

2、使用观察者模式,完成发布订阅。发布订阅者模式可以看:观察者模式

     1)模板里使用data对象属性的dom对象都订阅。

     2)当data对象里的属性的值发生变化时,就会发布,发布时,就改变了dom里的内容。

以下为源码:

     这个代码只是模拟数据绑定的原理,并没有考虑vue的虚拟dom和异步更新队列等问题。

1、模拟vue.js的代码(文件名:vue.js)

 class Vue{
        constructor(obj){
            //2、数据挂载
            //2.1) 、把obj对象的每个属性,作为vue对象的属性(属性名前面加上 $)
            for(let key in obj){
                this["$"+key] = obj[key];
            }
            //2.2)、把obj.data对象的属性,作为vue对象的属性,并且要有数据劫持的功能和发布订阅的功能
            let myObserver = {};
            observer.make(myObserver);
            for(let key in obj.data){
                this["_"+key] = obj.data[key];
                Object.defineProperty(this,key,{
                    //数据劫持
                    set:function(newValue){
                        //数据劫持的目的是,当key发生变化时,需要渲染页面
                        this["_"+key] = newValue;
                        //数据变了,就得渲染页面,发布订阅功能
                        myObserver.publish(newValue);
                    },
                    get:function(){
                        return this["_"+key];
                    }
                });
            }
     
            //2.3)、订阅(模板里的标签需要订阅data中的数据)
            let box = document.getElementById(obj.el.substring(1));//"box"
            //2.3.1)查找使用msg属性的dom元素,放在数组msgDom里
            let msgDom = [];
            for(let i=0;i<box.children.length;i++){
                if(box.children[i].innerHTML.includes("{{msg}}")){
                    //放到数组里,为了订阅服务
                    msgDom.push({
                      "dom":box.children[i],
                      "vinnerHTML":box.children[i].innerHTML// 原始的html,即:没有进行模板渲染的html
                    });
                    //把里面的{{msg}}的内容得替换掉
                    box.children[i].innerHTML = box.children[i].innerHTML.replace(/\{\{msg\}\}/g,obj.data.msg)  
                }
            }
            //2.3.2) 让数组数组msgDom里的每个元素订阅msg的值。
            for(let i=0;i<msgDom.length;i++){
                myObserver.addSubscriber(function(msg){
                     msgDom[i].dom.innerHTML = msgDom[i].vinnerHTML.replace(/\{\{msg\}\}/g,msg)
                });
            }
        }
    }
     
    //观察者模式的代码
     
    var observer={
        //订阅:给数组里添加函数
        addSubscriber:function(cb){
            this.subscribers.push(cb);
        },
     
        //退订:删除数组里的函数
        removeSubscriber:function(cb){
            let index = this.subscribers.indexOf(cb);
            this.subscribers.splice(index,1);
        },
     
        //发布
        publish:function(what){
            this.subscribers.forEach(element => {
                if(typeof element =="function"){
                    element(what);
                }
            });
            
        },
     
        //让某个对象具备发布订阅功能
        make:function(obj){
            for(let key in this){
                obj[key] = this[key];
            }
            obj.subscribers = [];
        }
    }

2、使用模拟的vue的html文件的代码

 <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
        <div id="box">
            <span>信息:{{msg}}</span>
        </div>
        <input id="txt" type="text"  />
        <input type="button" value="修改" οnclick="fn()" />
    </body>
    </html>
    <!--这个vue.js是我们自己写的,不是尤玉溪的-->
    <script src="js/vue.js"></script>
    <script>
     
    let vm = new Vue({
        el:"#box",
        data:{
            msg:"hello"
        }
    });
     
    function fn(){
        // vm.msg = "你好";
        vm.msg = document.getElementById("txt").value;
    }
     
    </script>



 

发布了6 篇原创文章 · 获赞 21 · 访问量 3264

猜你喜欢

转载自blog.csdn.net/ahhqdyh/article/details/104845908