实现一个简化版的Vue

223 阅读3分钟

实现一个简化版的Vue

最近在阅读《深入浅出Vue.js》一书,做下记录。

Vue类

我们正常使用vue时时这样的

new Vue({
  el: '#app',
  data: {
    message: 'hello world'
  }
})

所以我们首先实现一个Vue类用于创建Vue对象,接收一个options对象用于初始化

class Vue {
  constructor(options) {
    this.$el = options.el
    this._data = options.data
    this.$data = this._data
    // 对data进行响应式处理
    new Observe(this._data)
  }
}

Observe类

在JS中有两种方法可以实现侦测对象,Object.definePropertyES6的Proxy ,在vue2中是使用Object.defineProperty来实现数据劫持的。

class Observe {
  constructor(data) {
    if(typeof data === 'object') {
      this.walk(data)
    }
  }
  // 这个方法用于遍历对象中的属性,并依次对其进行响应式处理
  walk(obj) {
    const keys = Object.keys(obj)
    for(let i=0,lens=keys.length; i < lens; i++) {
      // 对所有属性进行监听
      this.defineReactive(obj, keys[i])
    }
  }
  // 数据劫持
  defineReactive(obj, key) {
    // 如果属性是对象,则递归调用walk 
    if(typeof obj[key] === 'object') {
      this.walk(obj[key])
    }
    const dep = new Dep()
    const val = obj[key]
    Object.defineProperty(obj, key, {
      enumerable: true, // 能否通过for in访问属性
      configurable: true, // 能否修改属性的特性
      // get方法将Dep.target即Watcher对象添加到依赖集合中
      get() {
        // 这里创建watcher对象时会给Dep.target赋值
        if(Dep.target) {
          dep.addSubs(Dep.target)
        }
        return val
      },
      set(newVal) {
        val = newVal
        // 依赖的变更响应
        dep.notify(newVal)
      }
    })
  }
}

Dep类

Dep类有一个subs的数组用于保存依赖,这里的依赖是我们后面要定义的Watcher,即观察者

class Dep {
  static target = null
  constructor() {
    this.subs = []
  }
  addSubs(watcher) {
    this.subs.push(watcher)
  }
  notify(newVal) {
    for(let i=0,lens=this.subs.length; i<lens; i++) {
      this.subs[i].update(newVal)
    }
  }
}

Watcher类

Watcher类做的事情就是观察数据的变更,调用对应属性的get方法触发依赖手机,并在数据变更后执行响应的更新

let uid = 0
class Watcher {
  // vm即一个Vue对象,key要观察的属性,cb是数据变更后的回调
  contructor(vm, key, cb) {
    this.vm = vm
    this.uid = uid++
    this.cb = cb
    // 调用get触发依赖手机之前,把自身赋值给Dep.target静态变量
    Dep.target = this
    // 触发对象上代理的get方法,执行get添加依赖
    this.value = vm.$data[key]
    // 用完清空
    Dep.target = null
  }
  // 在调用set触发Dep的notify时要执行的update函数
  update(newValue) {
    // 发生变化是才变更
    if(this.value !== newValue) {
      this.value = newValue
      this.run()
    }
  }
  run() {
    this.cb(this.value)
  }
}

总结

Vue的响应式就是侦测数据的变化,当数据发生变化时,要能侦测到并发出通知。

Object可以通过defineProperty将属性转换成getter/setter的形式追踪变化,读取数据时触发getter,修改数据时触发setter。

收集依赖需要为依赖找一个存储以来的地方,所以创建了Dep,它用来收集依赖、删除依赖和向依赖发送消息等。

所谓的依赖,其实就是Watcher。只有Watcher触发的getter才会收集依赖,哪个Watcher出发了getter就把哪个Watcher收集到Dep中,当数据发生变化时会循环依赖列表,把所有的Watcher都通知一遍。

Watcher的原理是先把自己设置到全局唯一的指定位置,然后读取数据。因为读取了数据所以会触发这个数据的getter。接在在getter中就会从全局唯一的那个位置读取当前正在读取数据的Watcher,并把这个Watcher收集到Dep中去。通过这样的方式,Watcher可以主动去订阅任意一个数据的变化。

此外我们创建了Observer类,它的作用是把一个object中的所有数据包括子数据都转换成响应式的。

如图所示,Data通过Observer转换成了getter/setter的形式来追踪变化。

当外界通过Watcher读取数据时,会触发setter,从而向Dep中的依赖发送通知。

Watcher接收到通知后,会向外界发送通知,变化通知到外界后可能会触发视图更新也有可能触发用户的某个回调函数等。