《深入浅出Vue.js》读书笔记1-Object的变化侦测

简介: 《深入浅出Vue.js》读书笔记1-Object的变化侦测

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)
    }
  }


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



网络异常,图片无法展示
|


那从头捋一次。


网络异常,图片无法展示
|


以上。

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只能侦测一个数据是否被修改,无法跟踪属性的删除和新增,就导致了对象里新增和删除属性无法被侦测到的问题。


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


9.总结


本章总结:


如何一步一步完成整个流程的:


网络异常,图片无法展示
|


这个流程是如何运转的:


网络异常,图片无法展示
|

相关文章
|
14天前
|
JavaScript 前端开发 大数据
在JavaScript中,Object.assign()方法或展开语法(...)来合并对象,Object.freeze()方法来冻结对象,防止对象被修改
在JavaScript中,Object.assign()方法或展开语法(...)来合并对象,Object.freeze()方法来冻结对象,防止对象被修改
10 0
|
2月前
|
存储 JavaScript 前端开发
JS篇(Array、Object)
JS篇(Array、Object)
17 1
|
3月前
|
JavaScript 前端开发 开发者
Vue.js 响应式变革来袭!结合热点技术,探索从 Object.defineProperty 到 Proxy 的奇妙之旅,触动你的心
【8月更文挑战第30天】在 Vue.js 中,响应式系统自动追踪并更新数据变化,极大提升了开发体验。早期通过 `Object.defineProperty` 实现,但存在对新旧属性处理及数组操作的局限。Vue 3.0 引入了 `Proxy`,克服了上述限制,提供了更强大的功能和更好的性能。实践中,可根据项目需求选择合适的技术方案,并优化数据操作,利用懒加载等方式提升性能。
40 0
|
4月前
|
JSON JavaScript API
JS【详解】Map (含Map 和 Object 的区别,Map 的常用 API,Map与Object 的性能对比,Map 的应用场景和不适合的使用场景)
JS【详解】Map (含Map 和 Object 的区别,Map 的常用 API,Map与Object 的性能对比,Map 的应用场景和不适合的使用场景)
80 0
|
6月前
|
JavaScript 前端开发 索引
JavaScript与Object C的区别
JavaScript与Object C的区别
33 1
|
6月前
|
前端开发 JavaScript
前端 js 经典:Object 常用原生方法
前端 js 经典:Object 常用原生方法
99 2
|
6月前
|
JavaScript
js 字符串String转对象Object
该代码示例展示了如何将一个以逗号分隔的字符串(`&#39;1.2,2,3,4,5&#39;`)转换为对象数组。通过使用`split(&#39;,&#39;)`分割字符串并`map(parseFloat)`处理每个元素,将字符串转换成浮点数数组,最终得到一个对象数组,其类型为`object`。
353 2
|
6月前
|
JavaScript 前端开发
JavaScript中Object.prototype.toString.call()、instanceOf和Array.isArray()的区别
JavaScript中Object.prototype.toString.call()、instanceOf和Array.isArray()的区别
75 1
|
6月前
|
JavaScript
JS之Object.defineProperty方法
JS之Object.defineProperty方法
|
6月前
|
JavaScript 前端开发 Java
编程笔记 html5&css&js 073 JavaScript Object数据类型
编程笔记 html5&css&js 073 JavaScript Object数据类型