八小时实现迷你版vuejs三:实现数据响应化

来源:互联网 发布:淘宝衣服店铺介绍 编辑:程序博客网 时间:2024/05/19 10:41

上一篇我们比较overview的讲了vuejs 的架构,从这一篇开始我们来自己动手实现一个vuejs。

首先我们实现数据响应化,数据响应化的意思就是我们要能监听到任何数据变动。比如执行了 this.name = “Yao Ming” 之后,我们要能监听到这个改动。那么怎么实现呢?我们需要借助 ES5 提供的新特性 getter/setter

构造函数 和 初始化

首先我们需要写一个 Vue 的构造函数,这里我们直接用ES6的Class语法实现一个Vue类:

class Vue {  constructor (options) {    this.init(options)  }  /**   * 初始化的入口   */  init (options) {    //...    const el = document.querySelector(options.el)    this._initState()    //...  }}

这里省略了不相关的代码,完整代码请参阅tiny-vue。在 init 函数里面我们会调用 this._initState() 来做state的初始化,为了方便起见,这里我们只处理 data,而跳过对 props 的处理。
为了更好的封装代码,我们把对state的处理放在了 state.js 中。

Proxy

当我们创建一个Vue实例的时候是这样的:

new Vue({  data: { //xxx}})

而访问data的时候是这样的 this.name=“xxx”。显然vuejs会读取data中定义的数据,并把它“代理”到 this 上,这一步是也是通过getter/setter 来实现的。

思路就是:遍历 data 的所有 key ,然后在 this 上对每一个key也做一个 getter/setter ,在 getter/setter 内部依然去访问 data 上对应的key。

为了方便起见,首先给 data 做一个别名 this._data = options.data
这是我们在 state.js 中的主要代码:

  Vue.prototype._initData = function () {    var dataFn = this.$options.data    var data = this._data = dataFn ? ( typeof dataFn == 'function' ? dataFn() : dataFn ) : {}    var keys = Object.keys(data)    var i, key    i = keys.length    while (i--) {      key = keys[i]      this._proxy(key)    }    // observe data    observe(data, this)  }     Vue.prototype._proxy = function (key) {          // need to store ref to self here          // because these getter/setters might          // be called by child scopes via          // prototype inheritance.          var self = this          Object.defineProperty(self, key, {               configurable: true,               enumerable: true,               get: function proxyGetter () {                    return self._data[key]               },               set: function proxySetter (val) {                    self._data[key] = val               }          })  }

这样,当我们在 this 上做读写操作的时候,实际上是对 this._data 做的操作。

Observer

下一步,我们需要检测任何对 this._data 的读写操作,在 vuejs中,这是通过 Observer 来实现的。

Observer 的作用就是:对传入的数据设置 getter/setter ,当调用 getter 时收集依赖,当调用 setter 时通知依赖。

暂时我们不用太纠结这个依赖 是什么,在后面讲到 Watcher 的时候会仔细讲这部分。
所以 Observer 做的事情其实和上一步的 Proxy 很像。那么这里就有一个很有意思的问题,为什么不直接在 proxy 的getter和setter中去做Observe要做的事呢?这是因为vuejs还有一个 this.$data 的API,如果在proxy中做了,那么直接使用this.$data.name 就无法触发我们 getter/setter,所以还是得在 this._data 本身上来实现。

我们需要创建一个 Observer 类:

import Dep from  './dep.js'function Observer(value) {  this.value = value  this.dep = new Dep()  // TODO: support Array  this.walk(value)}// Instance methods/*** Walk through each property and convert them into* getter/setters. This method should only be called when* value type is Object.** @param {Object} obj*/Observer.prototype.walk = function (obj) {  var keys = Object.keys(obj)  for (var i = 0, l = keys.length; i < l; i++) {    this.convert(keys[i], obj[keys[i]])  }}/*** Convert a property into getter/setter so we can emit* the events when the property is accessed/changed.** @param {String} key* @param {*} val*/Observer.prototype.convert = function (key, val) {  defineReactive(this.value, key, val)}// …export function observe (value, vm) {  const ob = new Observer(value)  ob.addVm(vm)  return ob}/*** Define a reactive property on an Object.** @param {Object} obj* @param {String} key* @param {*} val*/export function defineReactive (obj, key, val) {  var dep = new Dep()  //...  Object.defineProperty(obj, key, {    enumerable: true,    configurable: true,    get: function reactiveGetter () {      var value = getter ? getter.call(obj) : val      if (Dep.target) {        dep.depend()      }      return value    },    set: function reactiveSetter (newVal) {      var value = getter ? getter.call(obj) : val      if (newVal === value) {        return      }      val = newVal      dep.notify()    }  })}

然后我们在 initData 中调用 observer(data) 就可以把 data 变成响应式的。

当执行 this.name =“xxx” 的时候,代码最终会进到 defineReactive 中的 reactiveSetter 中去。

这里的Dep其实是记录了 ObserverWatcher 的依赖关系,因为很可能存在多个 watcher 依赖同一个 observer 的情况,所以 Observer 中会创建一个 dep 用来记录到底哪些 watcher 依赖他。

单纯看数据响应化是比较简单的。下一步我们开始讲 Directive 的实现的时候就会比较复杂一些,也会涉及到 Observer 相关的内容。

注意:上面的代码并不是完整的,并且省略了一些比较简单的 (比如 lifecycle.js)代码,完整代码请参阅 tiny-vue

上一篇:Vuejs架构