此面试题浅层次的解释在:前端面试题: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="修改" onclick="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>

 

Logo

前往低代码交流专区

更多推荐