当前位置:   article > 正文

Vue2响应式原理总结_vue响应式原理掘金

vue响应式原理掘金
  • vue响应式用了观察者的设计模式,响应式data的数据被修改,观察者会进行视图更新或者执行回调

1.用Observer类将对象变成响应式

  • 遍历对象的每个属性:
  • 给对象的每个属性创建Dep依赖收集器
  • Object.defineProperty给对象的每个属性定义setget方法:

          get:使用Dep来收集观察者

          setDep派发通知给收集到的观察者

  • 如果对象的属性也是一个对象,进行递归,重复以上操作
  1. class Observer {
  2. constructor (value: Object) {
  3. this.walk(value)
  4. }
  5. walk (obj: Object) {
  6. const keys = Object.keys(obj)
  7. for (let i = 0; i < keys.length; i++) { // 遍历对象的每个key
  8. defineReactive(obj, keys[i], obj[keys[i]])
  9. }
  10. }
  11. }
  12. const observe = (value: any) => {
  13. if (!isObject(value)) {
  14. return
  15. }
  16. return new Observer(value)
  17. }
  18. function defineReactive (
  19. obj: Object,
  20. key: string,
  21. val: any
  22. ) {
  23. const dep = new Dep() //给该属性创建依赖收集器
  24. observe(val) // 该属性可能是一个对象,用observe递归
  25. Object.defineProperty(obj, key, {
  26. enumerable: true,
  27. configurable: true,
  28. get: function reactiveGetter () {
  29. dep.depend() // 进行依赖收集,收集观察者
  30. return val
  31. },
  32. set: function reactiveSetter (newVal) {
  33. val = newVal
  34. observe(newVal) // 新的属性值可能是一个对象,用observe进行响应式处理,也就是重复以上的流程
  35. dep.notify() // 收集器派发通知给观察者
  36. }
  37. })
  38. }

2.Dep依赖收集器,收集观察者

  • 上面get方法里面使用了dep.depend收集观察者,我们假设观察者是一个函数,保存在window.target上
  •  那么dep收集观察者的逻辑就可以这么写:
  1. export default class Dep {
  2. constructor () {
  3. this.id = uid++
  4. this.subs = [] //收集器用来存放观察者的数组
  5. }
  6. addSub (sub: Watcher) {
  7. this.subs.push(sub)
  8. }
  9. depend () {
  10. if (window.target) {
  11. this.addSub(window.target) // 把观察者添加到subs数组上
  12. }
  13. }
  14. notify () {
  15. const subs = this.subs.slice()
  16. for (let i = 0, l = subs.length; i < l; i++) {
  17. subs[i].update()
  18. }
  19. }
  20. }

3.Watcher观察者

  • Watcher是一个中介,当对象变化时通过dep.notify() 通知它,它再通知其他的地方
  • 先看一下Watcher经典的使用方式
  1. vm.$watch('user.name', function() {
  2. // doing sometings
  3. })
  • 该函数代表user对象的name属性发生变化时,执行回调函数
  • 上面有说过对象的每个属性都会创建一个Dep,并且在该属性的get方法上进行依赖收集。我们只需要触发name属性的get方法,把这个watcher实例添加到name属性的Dep上就行了
  1. class Watcher {
  2. constructor (
  3. vm: Component,
  4. expOrFn: string | Function,
  5. cb: Function,
  6. ) {
  7. this.vm = vm
  8. this.cb = cb
  9. this.getter = parsePath(expOrFn) // 解析user.name,生成访问name属性的函数
  10. this.value = this.get()
  11. }
  12. get () {
  13. window.target = this
  14. const vm = this.vm
  15. // getter是可以访问name属性的函数
  16. // 此时会触发name属性的get函数执行dep.depend收集观察者
  17. // 观察者在上面一步已经赋值到了window.target上了,dep.depend可以进行收集
  18. let value = this.getter.call(vm, vm)
  19. window.target = undefined // 收集完毕,清空window.target
  20. return value
  21. }
  22. // 当name属性被修改时,触发dep.notify。notify函数会执行观察者的update方法
  23. update () {
  24. const oldValue = this.value
  25. this.value = this.get()
  26. this.cb.call(this.vm, this.value, oldValue) // 执行$watch('user.name', cb)的回调函数
  27. }
  28. }

4.Array响应式

  • Array能够改变自身内容的方法有7个:push、pop、shift、unshfit、sort、splice、reverse
  • 我们可以对数组的这些函数进行修改、在这些函数当中执行dep.notify通知观察者:
  1. const arrayProto = Array.prototype
  2. export const arrayMethods = Object.create(arrayProto)
  3. const methodsToPatch = [
  4. 'push',
  5. 'pop',
  6. 'shift',
  7. 'unshift',
  8. 'splice',
  9. 'sort',
  10. 'reverse'
  11. ]
  12. methodsToPatch.forEach(function (method) {
  13. const original = arrayProto[method]
  14. def(arrayMethods, method, function mutator (...args) {
  15. const result = original.apply(this, args)
  16. // todo。这里执行通知观察者的逻辑
  17. return result
  18. })
  19. })
  • 我们微调一下Observer的代码,把以上重写后的数组方法赋值给数组 
  1. class Observer {
  2. constructor (value: Object) {
  3. if(Array.isArray(value)) {
  4. value.__proto__ = arrayMethods // __proto__ 有兼容性问题 兼容性处理省略
  5. }else {
  6. this.walk(value)
  7. }
  8. }
  9. }
  • 我们重写了数组的方法,在这些方法里面执行Dep的通知。但我们还没有为数组创建Dep。
  • 上面说到对象是遍历每个属性,为每个属性创建一个Dep,那么数组的Dep应该在那里创建呢?
  • 我们可以在存放在Observer的实例上,Observer再次调整如下:
  1. class Observer {
  2. constructor (value: Object) {
  3. this.dep = new Dep() // 新增
  4. def(value, '__ob__', this) // 新增 在数组上添加__ob__属性指向当前的Observer实例
  5. if(Array.isArray(value)) {
  6. value.__proto__ = arrayMethods
  7. }else {
  8. this.walk(value)
  9. }
  10. }
  11. }
  • 这样数组就可以通过__ob__属性访问到dep了,调整arrayMethods如下:
  1. methodsToPatch.forEach(function (method) {
  2. const original = arrayProto[method]
  3. def(arrayMethods, method, function mutator (...args) {
  4. const result = original.apply(this, args)
  5. this.__ob__.dep.notify() // 这里执行通知观察者的逻辑
  6. return result
  7. })
  8. })
  • 通知观察者的逻辑实现完了,现在实现收集观察者的逻辑,我们调整defineReactive的代码如下:
  1. function defineReactive (
  2. obj: Object,
  3. key: string,
  4. val: any
  5. ) {
  6. const dep = new Dep()
  7. const childOb = observe(val)
  8. Object.defineProperty(obj, key, {
  9. enumerable: true,
  10. configurable: true,
  11. get: function reactiveGetter () {
  12. if(childOb) { // 新增
  13. childOb.dep.depend() // 新增
  14. }
  15. dep.depend()
  16. return val
  17. },
  18. set: function reactiveSetter (newVal) {
  19. val = newVal
  20. observe(newVal)
  21. dep.notify()
  22. }
  23. })
  24. }
  • 以上已经处理好了数组的响应式,但是数组里面的每个元素却不是响应式的,我们再一次调整Observer:
  1. class Observer {
  2. constructor (value: Object) {
  3. if(Array.isArray(value)) {
  4. value.__proto__ = arrayMethods
  5. this.observerArray(value) // 新增
  6. }else {
  7. this.walk(value)
  8. }
  9. }
  10. }
  11. observeArray (items: Array<any>) {
  12. for (let i = 0, l = items.length; i < l; i++) {
  13. observe(items[i])
  14. }
  15. }

掘金peng_YT 的个人主页 - 动态 - 掘金

语雀Peng_YT · 语雀

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号