ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

vue.js的响应式原理,理解为什么修改数据视图会自动更新

2021-09-25 11:03:16  阅读:159  来源: 互联网

标签:function vue obj val vm 视图 自动更新 return data


4rrHYT.jpg

如何追踪变化

在js中,有两种方法可以侦测到数据的变化:Object.defineProperty和Es6的Proxy。这里讨论的是vue2的响应式原理,所以就说Object.defineProperty,在vue3中使用的是Proxy,还没有开始看呢。
那么Object.defineProperty是如何侦测到对象的变化呢?如下

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

上面将Object.defineProperty进行了封装,封装好之后,每当data中值被读取就会触发get;每当设置data中的数据时,set函数就会触发

那么在哪里收集呢

把用到数据的地方收集起来,等到数据变化的时候再将收集好的依赖循环触发一次。(在getter中收集依赖,在setter中触发依赖)

依赖被收集在哪里

在每个key上面设置一个数组,专门去收集使用到key的地方,即依赖。然后这个数组我们取名dep,用来存储被收集的依赖。假设依赖是一个函数,保存在window.target上。然后我们来改造一下defineReactive:

function defineReactive(data, key, val) {
      let dep = [] //新增
      Object.defineProperty(data, key, {
        enumerable: true,
        configurable: true,
        get: function () {
          dep.push(window.target) //新增
          return val
        },
        set: function(newVal) {
          if(val === newVal) {
            return 
          }
          for(let i in dep) {
            dep[i](newVal, val)
          }
          val = newVal
        }
      })
    }

然后将dep封装成一个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 in subs) {
          subs[i].update()
        }
      }
    }

    function remove(arr, item) {
      if(arr.length) {
        const index = arr.indexOf(item)
        if(index > -1) {
          return arr.splice(index, 1)
        }
      }
    }

依赖是什么?

上面说,依赖是window.target,那么它又是什么呢?我们究竟要收集什么东西?
收集谁,就是当属性发生变化时,通知谁。我们要通知使用数据的地方,但是使用数据的地方有很多,类型也不同,可能是模板也可以是其他地方,所以我们要封装一个能处理所有情况的类,就叫做Watcher。所以,依赖就是Watcher,我们收集的也是Watcher。

什么是Watcher

Watcher是一个中间角色,数据发生变化时通知它,它再通知其他地方。
关于Watcher,先来看一个经典的使用方式:

vm.$watch('a.b.c', function(newVal, oldVal) {
  //do someting
})

当data.a.b.c发生变化时,触发第二个参数中的函数。
那么怎么去实现这个功能呢?首先要把这个watcher实例添加到data.a.b.c的Dep中去,然后当它改变的时候,通知watcher,watcher再执行里面的回调函数就行了。
那么实际代码如下:

    export default class Watcher {
      constructor (vm, expOrFn, cb) {
        this.vm = vm
        //执行一下this.getter就能读取到data.a.b.c的内容
        this.getter = parsePath(expOrFn) //读取字符串的keyPath
        this.cb = cb
      }

      get() {
        window.target = this //将window.target设成当前watcher实例
        let value = this.getter.call(this.vm, this.vm) //触发getter,将window.target添加到dep中
        window.target = undefined
        return value
      }

      update() {
        const oldValue = this.value //老数据
        this.value = this.get() //新数据
        this.cb.call(this.vm, this.value, oldValue) //回调函数
      }
    }

    const bailRE = /[^\w.$]/
    function parsePath (path) {
      if(bailRE.test(path)) {
        return
      }
      const segments = path.split('.')
      return function (obj) {
        for(let i in segments) {
          if(!obj) return
          obj = obj[segments]
        }
        return obj
      }
    }

4rrbfU.md.jpg

递归侦测所有key

上面已经能实现变化侦测的功能了,但是只能侦测数据中的一个属性,我们希望能把数据中的所有属性都侦测到,所以需要封装一个Observer类。这个类的作用就是将数据中的所有属性都转换成getter/setter的形式,然后去追踪它们的变化。

    export class Observer {
      constructor (value) {
        this.value = value
        if(!Array.isArray(value)) { //判断是不是数组,数组需要单独进行特殊处理
          this.walk(value)
        }
      }

      //walk会将每一个属性转换成getter/setter,并且只有在数据类型是对象才会调用
      walk(obj) {
        const keys = Object.keys(obj)
        for(let i in keys) {
          defineReactive(obj, keys[i], obj[keys[i]])
        }
      }
    }

然后再将defineReactive修改一下:

    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() //调用watcher中的回调函数去通知所有的依赖
        }
      })
    }

现在就大功告成了!!!

关于Object的添加和删除问题

有一些语法中即使数据发生了变化,vue.js也追踪不到。
比如,向object添加属性:

    var vm = new Vue({
      el: '#el',
      template: '#demo-template',
      methods: {
        action() {
          this.obj.name = 'berwin' //不能实时监听
        }
      },
      data: {
        obj: {}
      }
    })

再比如,删除一个属性:

    var vm = new Vue({
      el: '#el',
      template: '#demo-template',
      methods: {
        action() {
          delete this.obj.name  //不能实时监听
        }
      },
      data: {
        obj: {name: 'sifan'}
      }
    })

Object.defineProperty只能追踪一个数据是否被修改,无法追踪新增属性和删除属性,所以才会出现上面的问题。
但是也不用担心,vue.js提供了两个API——vm.\(set和vm.\)delete,之后再更它们两个的原理。

标签:function,vue,obj,val,vm,视图,自动更新,return,data
来源: https://www.cnblogs.com/taosifan/p/15329016.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有