vue双向绑定--自己的简单实现

vue的双向绑定仿写–简单实现
代码会放到最后--------------------------------------------

了解过vue的都知道老版vue使用的是defineproperty进行双向绑定,但是应对面试最好自己实现一下,了解一下实现也是面试的加分项,下面我来讲解一下我写的主要的原理以及实现过程----
主要原理就是发布订阅机制-----------(不了解的可以去学习一下,下期可能会介绍一下,其实发布订阅就是dom2)
1.new vue({data对象},对应的dom元素,完成双向绑定data对象中的一个内容)
首先 了解过vue的都知道使用 vue 肯定有一个vue实例,new vue()。所以我进行仿写的时候也先创建vue实例。
observer()
new watcher() 先不用管


```javascript
 class Vue{
                    constructor(data,vm,exp){//data就是数值的存放
                            this.data=data
    //Object.keys把键值名转化为数组,然后通过foreach完成set ,get的绑定,也就是 es5访问器属性
                            Object.keys(this.data).forEach((value)=>{ //把对象的值转为数组然后执行Proxyproperty()
                               this.Proxyproperty(value)
                            })
                            observer(data)
                            vm.innerHTML = this.data[exp];  // 初始化模板数据的值
                            new Watcher(this, exp, function (value) {
                            vm.innerHTML = value;
                            });
                        return this;
                        }   
                            //defineproperty 使用set get 完成绑定 
                    Proxyproperty(key){
                            Object.defineProperty(this,key,{
                                enumerable:false,
                                configurable:true,
                                get(){
                                    return this.data[key]
                                }
                                ,set(newVal){
                                    this.data[key]=newVal
                                }
                            })
                        }
                        } 

2.observer()方法 这里的原理就是发布订阅机制
把需要存放的值放到一个Dep()对象中。var dep=new Dep(),该对象里面
有对应的subs数组this.subs=[] ,dep对象中有方法add为了添加数组,notify()检测里面所有元素,然后变成数组里面对应的数据,在这里可能就会有不懂的疑问了,为啥notify里面有一个update方法,可是在原型里面也没有啊,为啥在watcher对象里面有,执行的是watcher对象的数组,因为通过watcher完成的数据的绑定,如果watcher里面不监听任何的东西,observer里面就不会有任何的东西存在,所以sub可以对应执行update方法,如果还不懂,到watch里面在进行讲解(仔细)

 function Observer(data){
                    this.data =data;
                    this.walk(data)
               }
               Observer.prototype={

                defineReactive(data,key,val){ //data就是vue的对象 key就是键名 val就是键名对应的键值

                    var dep=new Dep()
                    var childObj = observer(val); //深度调用,检测是否是对象
                    Object.defineProperty(data,key,{
                        configurable:true,
                        enumerable:false,
                        get(){
                            if (Dep.target) {
                                dep.add(Dep.target); //如果增值不为空,就是放到sub数组中
                                            }

                            return val;
                        },
                        set: function(newVal) {
                                if (newVal === val) {
                                    return;
                                        }
                                 val = newVal;
                                dep.notify();
                                                }
                    })
                   }
                   ,walk(data){
                    let _this=this
                    Object.keys(data).forEach(function(key) {
                            _this.defineReactive(data, key, data[key]);//监听this的每个变化
                                        //这里的this指向的是window ,因为闭包。所以使用_this
                        });
                   }
                   
               }


               function observer(value) {
                 if (!value || typeof value !== 'object') {
                    return;
                    }
                    return new Observer(value);
                };
                //---发布订阅机制
               class Dep{
                   constructor(){
                       this.subs=[]
                   }
                   add(value){ //增加值
                        this.subs.push(value)   
                   }
                   notify(){ //更新
                        this.subs.forEach(function(sub){
                            sub.update()
                        })
                   }
               }
               
               Dep.target=null

如果认真看了上面代码的内容的话,就会发现observer尽管data中有内容,但是get方法是不会把对应的值 存入数组的,因Dep.target=null,所以没啥用,所以就一定需要一部可以把Dep.target=null不让其为空的一部,所以就会出现watcher()

    function Watcher(vm, exp, cb) {
        this.cb = cb;//回调函数
        this.vm = vm;//对应this,也就是vue的实例
        this.exp = exp;//data中对应绑定的值
        this.value = this.get();  // 将自己添加到订阅器的操作
                        }

Watcher.prototype = {
    update: function() {
        this.run();
    },
    run: function() {
        var value = this.vm.data[this.exp];
        var oldVal = this.value;
        if (value !== oldVal) {
            this.value = value;
            this.cb.call(this.vm, value, oldVal);
        }
    },
    get: function() {
        Dep.target = this;  // 缓存自己
        var value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
        Dep.target = null;  // 释放自己
        return value;
    }
};

这里非常的关键,尽管看上去代码很少, 这里一定要自己看,或者听我说,原型上get很关键,因为get是这里的核心,这是实现的点睛之笔,首先如果调用new watcher对象,就会执行里面对应的方法,就会执行get()方法,这时就会把dep.target变成this,就不再是null了,所以在这个时候调用可以引用调用data的值,就触发observer中的get方法,就会把对应的内容存放到subs的数组中,执行完之后,再让 Dep.target = null,不让其执行。如果不调用watch,就不让外界改变或增加subs数组
中的内容。这也是vue2中-watch可以监听变化的原因。

update方法还没有讲清楚,这里再讲一次,还是那个问题,为啥Dep里面的notify方法可以调用watcher里面的update,如果你看懂了上面的代码,现在你应该了解到了watcher和observer的联系, 因为每次subs数组的改变都是因为watch监听的变化,因为只有watcher中Dep.target不为null,所以,触发的环境是在watcher中的。这也就是为啥它可以调用原型的方法。

~~---------------------------------------------------------------------------~

如果哪里写的不对,欢迎指出,毕竟我也是新手,可以写在评论区,下面我会把代码全部放到底下,方便大家了解。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

 
</head>
<body>
    
    <div id="app">
        {{name}}
    </div>



    <script>
               function Observer(data){
                    this.data =data;
                    this.walk(data)
               }
               Observer.prototype={

                defineReactive(data,key,val){ //data就是vue的对象 key就是键名 val就是键名对应的键值

                    var dep=new Dep()
                    var childObj = observer(val); //深度调用,检测是否是对象
                    Object.defineProperty(data,key,{
                        configurable:true,
                      
                        get(){
                            if (Dep.target) {
                                dep.add(Dep.target); //如果增值不为空,就是放到sub数组中
                                            }

                            return val;
                        },
                        set: function(newVal) {
                                if (newVal === val) {
                                    return;
                                        }
                                 val = newVal;
                                dep.notify();
                                                }
                    })
                   }
                   ,walk(data){
                    let _this=this
                    Object.keys(data).forEach(function(key) {
                            _this.defineReactive(data, key, data[key]);//监听this的每个变化
                                        //这里的this指向的是window ,因为闭包。所以使用_this
                        });
                   }
                   
               }


               function observer(value) {
                 if (!value || typeof value !== 'object') {
                    return;
                    }
                    return new Observer(value);
                };
                //---发布订阅机制
               class Dep{
                   constructor(){
                       this.subs=[]
                   }
                   add(value){ //增加值
                        this.subs.push(value)   
                   }
                   notify(){ //更新
                        this.subs.forEach(function(sub){
                            sub.update()
                        })
                   }
               }
               
               Dep.target=null

    function Watcher(vm, exp, cb) {
        this.cb = cb;//回调函数
        this.vm = vm;//对应this
        this.exp = exp;//data
        this.value = this.get();  // 将自己添加到订阅器的操作
                        }

Watcher.prototype = {
    update: function() {
        this.run();
    },
    run: function() {
        var value = this.vm.data[this.exp];
        var oldVal = this.value;
        if (value !== oldVal) {
            this.value = value;
            this.cb.call(this.vm, value, oldVal);
        }
    },
    get: function() {
        Dep.target = this;  // 缓存自己
        var value = this.vm.data[this.exp]  // 强制执行监听器里的get函数
        Dep.target = null;  // 释放自己
        return value;
    }
};
               
               
               
               
               
               class Vue{
                    constructor(data,vm,exp){
                            this.data=data
                            Object.keys(this.data).forEach((value)=>{ //把对象的值转为数组然后执行Proxyproperty()
                               this.Proxyproperty(value)
                            })
                            observer(data)
                            vm.innerHTML = this.data[exp];  // 初始化模板数据的值
                            new Watcher(this, exp, function (value) {
                            vm.innerHTML = value;
                            });
                        return this;
                        }   
                            //defineproperty 使用set get 完成绑定 
                    Proxyproperty(key){
                            Object.defineProperty(this,key,{
                                enumerable:false,
                                configurable:true,
                                get(){
                                    return this.data[key]
                                }
                                ,set(newVal){
                                    this.data[key]=newVal
                                }
                            })
                        }
                        }      
    </script>
    <script>
    const appa =new Vue({name:'lyl',age:'18'}, app,'name')
    console.log(appa.name)
    appa.name=123
    console.log(appa.name)
    appa.age=20
    console.log(appa.age)
    </script>

</body>
</html>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值