最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • Vue深度学习系列(一)双向数据绑定

    正文概述 掘金(Beatrix)   2021-04-07   390

    一、理论知识

    Vue双向数据绑定原理:

    二、基本流程

    实现一个类似于vue双向数据绑定的响应式系统,以下是简易流程图

    Vue深度学习系列(一)双向数据绑定 由上面的图,关于监听器(Observer)、订阅器(Dep)、订阅者(Watcher)可能对没有学过设计模式的人有一点点抽象,那换个简单的例子说下他们的关系

    在以上信息中,我们抽离出小明这个人他就是一个订阅者实例,小红手上的户型A信息就是数据,小红就是监听器实例,而这个记录这些A户型意向购买者信息的小本本就是Dep实例。当户型A有开始售卖了,小红就赶紧通知小本本上的人。

    这里是举的一个小小的例子,可能不太恰当,详细具体的可以去看发布-订阅模式

    接下来,我们一点一点的实现一个响应式系统

    1.实现监听器Observer

    在这里我们将通过Object.defineProperty方法实现数据劫持,也就是说我们能感知数据对象的读写,使得数据对象变得可观测。

    • 关于ObjectdefineProperty

    这里就不展开细说了 详细的使用方法查看MDN。需要注意的就是属性描述符和存取描述符的使用

    接下来是coding时间:

    基本实现
    • defineReactive()方法实现属性的数据拦截
    function defineReactive(obj, key, value) {
      Object.defineProperty(obj, key, {
        get: function getter() {
          console.log("获取")
          return value;
        },
        set: function setter(newValue) {
          if (newValue === value) return;
          value = newValue
          console.log("设置")
        }
      })
    }
    
    • Observer类实现对象属性的遍历拦截
    class Observer {
      constructor(obj) {
        this.value = obj
        this.trave()
      }
      trave() {
        Object.keys(this.value).forEach((key) => defineReactive(this.value, key,this.value[key]))
      }
    }
    
    
    优化提升

    通过以上代码我们基本可以实现对一个对象obj所有属性的数据劫持,但是有一个小小的问题,如果对象属性是一个对象呢,也就是说obj内有嵌套属性,因此我们可以进行遍历,因此将代码优化为

    class Observer {
      constructor(obj) {
        this.value = obj
        this.trave()
      }
      trave() {
        Object.keys(this.value).forEach((key) => defineReactive(this.value, key,this.value[key]))
      }
    }
    function observe(obj){
      if(!obj||typeof obj!=='object'){
        return;
      }
      //监听器实例化
      new Observer(obj);
    }
    function defineReactive(obj, key, value) {
      //对每个属性值进行判断
      observe(value)
      Object.defineProperty(obj, key, {
        get: function getter() {
          console.log("获取")
          return value;
         
        },
        set: function setter(newValue) {
          if (newValue === value) return;
          value = newValue
          //对新设置的属性也需要进行判断
          observe(newValue)
          console.log("设置")
        }
      })
    }
    

    2.实现订阅者Watcher

    先来了解Watcher需要实现的功能是什么?

    1. 当实例化一个订阅者的时候,意味着需要获取它订阅的属性
    2. 当订阅者收到数据变化后,需要去调用相应的回调函数

    接下来coding时间:

    基本实现
    class Watcher{
      constructor(obj,exp,cb){
        this.obj = obj;
        //exp 为string类型 类似于'person.name'这样的string
        this.exp = exp;
        this.cb = cb;
        //实例化Watcher实例时获取它订阅的属性
        this.value = this.get()
      }
    //订阅属性
      get(){
        var value = getValue(this.obj,this.exp);
        return value;
      }
    //数据变化时,执行回调函数
      update(){
        var newVal = getValue(this.obj,this.exp);
        var oldVal = this.value;
        if(newVal!==oldVal){
          //数据更新
          this.value = newVal;
          //执行回调函数
          this.cb();
        }
      }
    }
    function getValue(obj,exp) {  
      var keys= exp.split(".");
      var value;
      for(let i of keys){
        if(!obj) return;
        value = obj[i];
    
      }
      return value;
    }
    
    

    3.实现订阅器Dep

    我们先来了解一下它需要实现的功能是什么?

    1. 每一个属性对应一个dep实例,dep实例存储每一个订阅该属性的Watcher
    2. 当数据发生变化时,对应的dep实例要去通知存储的每一个watcher
    基本实现
    class Dep{
      constructor(){
        this.subs = [];
      }
    //将Watcher实例添加进subs数组
      addSub(sub){
        this.subs.push(sub);
      }
    //派发消息 使得每个watcher实例调用update方法
      notify(){
        this.subs.forEach(function (sub) {  
          sub.update();
        })
      }  
    }
    
    优化提升

    经过上述的coding,我们只是进行了基本类的实现,而他们之间的联系还没有进行实现,具体是以下功能:

    1. 属性什么时候添加订阅器,属性改变时怎么通知对应的dep实例
    function defineReactive(obj, key, value) {
      //实例化dep
      var dep = new Dep();
      observe(value)
      Object.defineProperty(obj, key, {
        get: function getter() {
            
          return value;
         
        },
        set: function setter(newValue) {
          if (newValue === value) return;
          value = newValue;
          observe(newValue);
          //派发消息
            dep.notify();
        }
      })
    }
    
    1. 怎么将实例化watcher实例时添加到对应属性的dep实例中?
    1. 怎么getter方法中获取watcher实例?
    • 修改Watcher
    get(){
      //将watcher实例挂靠在getter方法能访问到地方
        Dep.target = this
        var value = getValue(this.obj,this.exp);
      //需要重置Dep.target  
        Dep.target = null;
        return value;
      }
    
    
    • 修改defineReactive函数
        get: function getter() {
        //判断Dep.target是否有值,如果有的话 添加watcher实例
            if(Dep.target){
            dep.addSub(Dep.target);
             return value;
          }
        }
    
    

    三、完整代码

    class Observer {
      constructor(obj) {
        this.value = obj
        this.trave()
      }
      trave() {
        Object.keys(this.value).forEach((key) => defineReactive(this.value, key,this.value[key]))
      }
    }
    function observe(obj){
      if(!obj||typeof obj!=='object'){
        return;
      }
      new Observer(obj);
    }
    function defineReactive(obj, key, value) {
      observe(value)
      //实例化dep
      var dep = new Dep();
      Object.defineProperty(obj, key, {
        get: function getter() {
          if(Dep.target){
            dep.addSub(Dep.target);
          }
          return value;
         
        },
        set: function setter(newValue) {
          if (newValue === value) return;
          value = newValue
          observe(newValue);
          dep.notify()
        }
      })
    }
    //watcher
    class Watcher{
      constructor(obj,exp,cb){
        this.obj = obj;
        this.exp = exp;
        this.cb = cb;
        //实例化Watcher实例时获取它订阅的属性
        this.value = this.get()
      }
    //订阅属性
      get(){
        Dep.target = this;
        var value = getValue(this.obj,this.exp);
        Dep.target = null
        return value;
    
      }
    //数据变化时
      update(){
        var newVal = getValue(this.obj,this.exp);
        var oldVal = this.value;
        if(newVal!==oldVal){
          //数据更新
          this.value = newVal;
          this.cb();
        }
      }
    }
    function getValue(obj,exp) {  
      var keys= exp.split(".");
      console.log(keys)
      var value;
      for(let i of keys){
        if(!obj) return;
        value = obj[i];
    
      }
      return value;
    }
    
    class Dep{
      constructor(){
        this.subs = [];
      }
    //将Watcher实例添加进subs数组
      addSub(sub){
        this.subs.push(sub);
      }
    //派发消息 使得每个watcher实例调用update方法
      notify(){
        this.subs.forEach(function (sub) {  
          sub.update();
        })
      }  
    }
    
    

    四、实现渲染

    通过以上步骤 我们就是实现了数据变化,相应的订阅者收到消息,类似于Vue.$watch。对于真正的view和data的双向绑定需要有进一步的具体的实现。4.8号之前找时间实现了


    下载网 » Vue深度学习系列(一)双向数据绑定

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元