vue响应式原理

思路:遍历data中的属性,将属性传入denfineReactive方法中,在defineReactive方法中通过Object.defineProperty重写getset方法。

观察者模式(Watcher, Dep)

   订阅:defineReactive中创建了Dep实例,在getter方法中会调用dep.depend(),将当前的Dep.target保存的watcher实例pushdep.subs当中。

  通知:在setter方法中,当数据更新时会调用dep.notfiy(),遍历subs中的watcher,调用watcher.update()触发相应操作。

 

dep类:

  subs:用于保存订阅了该depwatcher实例数组(subs)

  addSub:用于向subs数组中添加watcher实例;

  notify:用于遍历subs,调用每个watcherupdate方法进行更新操作;

class Dep {
  constructor () {
    this.subs = []
  }
  notify () {
    const subs = this.subs.slice()
    for (let i = 0; i < subs.length; i++) {
        subs[i].update()
    }
  }
  addSub (sub) {
    this.subs.push(sub)
  }
}

class Watcher {
  constructor () {
  }
  update () {
  }
}

let dep = new Dep()
dep.addSub(new Watcher()) // Watcher订阅了依赖

 

watcher:

  watcher类不太好理解,它有uptate方法,在其中调用了run方法,进行更新操作。

class Watcher () {
  constructor (vm, expOrFn, cb, options) {
    this.cb = cb
    this.value = this.get()
  }
  get () {
    pushTarget(this) // 标记全局变量Dep.target
    let value = this.getter.call(vm, vm) // 触发getter
    if (this.deep) {
      traverse(value)
    }
    popTarget() // 标记全局变量Dep.target
    return value
  }
  update () {
    this.run()
  }
  run () {
      const value = this.get() // new Value
      // re-collect dep
      if (value !== this.value ||
          isObject(value)) {
          const oldValue = this.value
          this.value = value
          this.cb.call(this.vm, value, oldValue)
      }
  }
}

defineReactive

 

function defineReactive (obj, key, val) {
    const dep = new Dep()
    const property = Object.getOwnPropertyDescriptor(obj, key)

    const getter = property && property.get
    const setter = property && property.set

    let childOb = observe(val)
    Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: function reactiveGetter () {
            const value = getter ? getter.call(obj) : val
            if (Dep.target) {
                dep.depend() // 等价执行dep.addSub(Dep.target),在这里收集
            }
            return value
        },
        set: function reactiveSetter (newVal) {
            const value = getter ? getter.call(obj) : val
            if (newVal === value) {
                return
            }
            if (setter) {
                setter.call(obj, newVal)
            } else {
                val = newVal
            }
            dep.notify()
        }
    })

 

由于defineReactive是在创建vue实例时进行调用,在实例创建后,修改data中的应用类型,比如给对象新增属性,新增的属性没有重写getset所以不是响应式的,数组也一样,所以vue提供了$set方法来为对象新增属性,同时为劫持数组的prototype重写了push、pop、splice等数组的操作方法,以实现数组操作的响应式操作。

 

重写Array.prototype中的方法:

// 函数拦截
/*
 思路:
 1.使用一个临时函数存储原函数(指向原函数)
 2.重新定义原函数(将原函数指向一个新的函数)
 3.在新的函数中扩展新功能,最后调用临时函数

*/
const ARR_METHODS = ['push', 'pop', 'splice']

let arr_methods = Object.create(Array.prototype) // 创建我们自己的原型__proto__
ARR_METHODS.forEach(way => {
  arr_methods[way] = function() {
    console.log(`拦截数组操作方法:${way}`)
    for(let i = 0; i < arguments.length; i++) {
      reactify(arguments[i])
    }
    Array.prototype[way].apply(this, arguments) // 调用原方法
  }
})
 
posted @ 2021-02-23 15:30  千昭。  阅读(20)  评论(0编辑  收藏  举报