最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 《深入浅出Vue.js》读书笔记1-Object的变化侦测

    正文概述 掘金(葛坪里)   2021-01-05   600

    Object变化侦测

    1.什么是变化侦测

    vue.js会自动通过状态生成DOM,并将其输出在页面上显示出来,这个过程叫做渲染

    在运行时,应用内部的状态会不断发生变化,此时需要不停的重新渲染,如何确定状态中发生了什么变化? 变化侦测就是来解决这个问题的。

    vue的变化侦测:状态发生变化时,vue.js立刻就知道哪些状态发生了变化,就可以进行更细粒度的更新。(ps:Angular和React的变化侦测都是在状态发生变化时,不知道哪个状态变了,只知道状态可能变了,然会会发送一个信号告诉框架,框架内部收到信号后,会进行一个暴力比对来找出哪些DOM节点需要更新。) 但是粒度越细,每个状态绑定的依赖就越多,依赖追踪在内存上开销越大。因此vue.js2.0引入了虚拟DOM,状态绑定的依赖不再是具体的DOM节点,而是一个组件,这样状态变化后,通知到组件组件内部再去使用虚拟dom去比对。

    2.如何追踪变化

    vue.js2.0使用的是Object.defineProperty实现,vue3.0使用的是proxy。

    function defineReactive(data,key,val){
      Object.defineProperty(data,key,{
        enumerable:true,
        configurable:true,
        get:function(){
          return val;
        },
        ser:function(newVal){
          if(val === newVal){
            return;
          }
          val = newVal;
        }
      })
    }
    

    这个函数defineReactive用来对Object.defineProperty封装,并定义了一个响应式的数据。封装好后,每当从data的key读取数据,get函数被触发,每当往data的key中设置数据时,set函数被触发。

    3.如何收集依赖

    我们观察数据,目的是当数据属性发生变化时,可以通知使用的地方。

    <template>
      <p>{{hancao}}</p>
    </template>
    

    对于上面的问题,要先收集依赖,把用到gepingli的地方收集起来,然后等属性发生变化时,把之前收集好的依赖循坏触发一遍就好。

    故,总结:在getter中收集依赖,在setter中触发依赖。

    4.依赖收集在哪

    首先,可以想到,每一个key都对应一个数组,用来存储当前key的依赖,假设依赖是一个函数,我们将其保存在window.target上(window.xxx都无所谓,只要是确定的位置就好),于是对defineReactive进行改造。

    //vue2.0
    function defineReactive(data,key,val){
      let dep = [];
      Object.defineProperty(data,key,{
        enumerable:true,
        configurable:true,
        get:function(){
          dep.push(window.target);//将依赖保存在dep数组中
          return val;
        },
        ser:function(newVal){
          if(val === newVal){
            return;
          }
          // 触发dep数组中的依赖
          for(let i =0;i<dep.length;i++){
            dep[i](newVal,val);// watch
          }
          val = newVal;
        }
      })
    }
    

    但是这样写有些耦合,可以把依赖收集的代码封装成dep类,使用这个类可以用来收集删除或者向依赖发送通知。

    export default class Dep {
      constructor(){
        this.subs = []
      }
      addSub(sub){
        this.subs.push(sub)
      }
      removeSub(sub){
        remove(this.subs,sub)
      }
      depend(){
        if(window.target){
          this.addSub(window.target)
        }
      }
      notify(){
        const subs = this.subs.slice()
        for(let i =0;i<subs.length;i++){
          subs[i].update()
        }
      }
    }
      function remove(arr,item) {
        if(arr.length){
          const index = arr.indexOf(item);
          if(index > -1){
            return arr.splice(index,1)
          }
        }
      }
    

    之后对defineReactive进行改造。

    function defineReactive(data,key,val){
      let dep = new Dep();
      Object.defineProperty(data,key,{
        enumerable:true,
        configurable:true,
        get:function(){
          dep.depend()
          return val;
        },
        ser:function(newVal){
          if(val === newVal){
            return;
          }
          val = newVal;
          dep.notify();
        }
      })
    }
    

    5.依赖是谁

    上面收集的window.target是什么,我们要收集的是谁?

    收集谁,就是当属性发生变化时,通知谁。

    我们通知用到数据的地方,有可能是模板,也有可能是用户写的watch。所以需要抽象出一个能集中处理这些的类。

    于是,给它起了一个好听的名字,就是hancao,不对,是Watcher。

    6.什么是watcher

    watcher是一个中介,数据发生变化时通知它,然后它再通知其他地方。

    就像房东房子要出租,告诉中介,中介再告诉你这有个房子要出租,房租正好是你一个月工资。

    Watch使用方式:

    
    vm.$watch('g.p.l',function (newVal,oldVal) {
      // 搞点事情
    })
    

    上面代码表示,当data.g.p.l发生变化的时候,触发第二个参数里面的方法。

    于是我们只需要把watch儿实例添加到data.g.p.l属性的Dep里面就好了,然后当这个值发生变化,通知Watcher,接着Watcher在执行参数中的这个回调函数。

    于是乎,下面这段代码便产生了:

    export default class Watcher {
        constructor (vm,exp,cb){
          this.vm = vm
          this.getter = parsePath(exp) // 比如 exp是 g.p.l。parsePath就是通过这个路径去获取data.g.p.l的内容
          this.cb  = cb
          this.value = this.get()
        }
        get (){
          window.target = this
          let value = this.getter.call(this.vm,this.vm)
          window.target = undefined
          return value    
        }
        update (){
          const oldVal = this.value
          this.value = this.get()
          this.cb.call(this.vm,this.value,oldVal)
        }
      }
    

    上面的代码可能第一次看到会有些懵,

    那从头捋一次。 《深入浅出Vue.js》读书笔记1-Object的变化侦测《深入浅出Vue.js》读书笔记1-Object的变化侦测 以上。

    parsePath根据路径读取data中的数据,简单的循环不在此多描述了。

    7.递归侦测所有key

    前面完成了Object的变化侦测功能(data的key),但是我们希望侦测对象中的所有属性(包括子属性)。

    export class Observer {
        constructor (value) {
          this.value = value
          if(!Array.isArray(value)){
            this.walk(value)
          }
        }
        walk(obj) {
          const keys = Object.keys(obj)
          for(let i =0 ;i<keys.length;i++){
            defineReactive(obj,keys[i],obj[keys[i]])
          }
        }
      }
       
      function defineReactive(data,key,val) {
        if(typeof val === 'object'){
          new Observer(val)
        }
        let dep = new Dep();
        Object.defineProperty(data,key,{
          enumerable:true,
          configurable:true,
          get:function(){
            dep.depend()
            return val;
          },
          set:function(newVal){
            if(val === newVal){
              return;
            }
            val = newVal;
            dep.notify();
          }
        })
      }
    

    上面的代码也比较好理解,

    Observer首先判断数据类型,只有Object类型的数据执行walk方法,walk方法对这个对象的obj进行遍历,执行defineReactive方法,在这里,其他的代码和前面的一毛一样,唯独,在最前面对val的类型进行了判断。如果是对象类型,则再一次执行new Observer的操作,这样做到了如果这个对象含有子对象,也会对子对象childOBj的属性进行变化侦测。

    于是通过Observer类,我们就将data中的所有属性(以及子属性)都转化成了getter/setter形式,可以侦测其变化的对象。

    也就是我们只要将一个object传入Observer类中,这个object就会变成响应式的reactiveObject。

    8.Object的问题

    由于vue2.0采用了Object.defineProperty来将对象的key转换成getter/setter的形式,来进行变化侦测,但是getter和setter只能侦测一个数据是否被修改,无法跟踪属性的删除和新增,就导致了对象里新增和删除属性无法被侦测到的问题。

    解决:可以采用setset和set和delete方法.

    9.总结

    本章总结:

    如何一步一步完成整个流程的: 《深入浅出Vue.js》读书笔记1-Object的变化侦测 这个流程是如何运转的: 《深入浅出Vue.js》读书笔记1-Object的变化侦测


    起源地下载网 » 《深入浅出Vue.js》读书笔记1-Object的变化侦测

    常见问题FAQ

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

    发表评论

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

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

    联系作者

    请选择支付方式

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