松鼠乐园 松鼠乐园
  • 注册
  • 登录
  • 首页
  • 快捷入口
    • Vue
    • Tensorflow
    • Springboot
    • 语言类
      • CSS
      • ES5
      • ES6
      • Go
      • Java
      • Javascript
    • 工具类
      • Git
      • 工具推荐
    • 服务器&运维
      • Centos
      • Docker
      • Linux
      • Mac
      • MySQL
      • Nginx
      • Redis
      • Windows
    • 资源类
      • 论文
      • 书籍推荐
      • 后端资源
      • 前端资源
      • html网页模板
      • 代码
    • 性能优化
    • 测试
  • 重大新闻
  • 人工智能
  • 开源项目
  • Vue2.0从零开始
  • 广场
首页 › Vue › 【3】实现迷你版vuejs:实现数据响应化

【3】实现迷你版vuejs:实现数据响应化

迦娜王
10月前Vue
154 0 0

上一篇我们比较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其实是记录了 Observer 和 Watcher 的依赖关系,因为很可能存在多个 watcher 依赖同一个 observer 的情况,所以 Observer 中会创建一个 dep 用来记录到底哪些 watcher 依赖他。

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

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

vue
0
【2】实现迷你版vuejs:vuejs 架构
上一篇
【4】实现迷你版vuejs:实现compile和Directive
下一篇
评论 (0)

请登录以参与评论。

现在登录
聚合文章
在Gitee收获近 5k Star,更新后的Vue版RuoYi有哪些新变化?
2月前
vue3.x reactive、effect、computed、watch依赖关系及实现原理
2月前
Vue 3 新特性:在 Composition API 中使用 CSS Modules
2月前
新手必看的前端项目去中心化和模块化思想
2月前
标签
AI AI项目 css docker Drone Elaticsearch es5 es6 Geometry Go gru java Javascript jenkins lstm mysql mysql优化 mysql地理位置索引 mysql索引 mysql规范 mysql设计 mysql配置文件 mysql面试题 mysql高可用 nginx Redis redis性能 rnn SpringBoot Tensorflow tensorflow2.0 UI设计 vue vue3.0 vue原理 whistle ZooKeeper 开源项目 抓包工具 日志输出 机器学习 深度学习 神经网络 论文 面试题
相关文章
在Gitee收获近 5k Star,更新后的Vue版RuoYi有哪些新变化?
vue3.x reactive、effect、computed、watch依赖关系及实现原理
Vue 3 新特性:在 Composition API 中使用 CSS Modules
新手必看的前端项目去中心化和模块化思想
松鼠乐园

资源整合,创造价值

小伙伴
墨魇博客 无同创意
目录
重大新闻 Centos CSS Docker ES5 ES6 Go Java Javascript Linux Mac MySQL Nginx Redis Springboot Tensorflow Vue Vue2.x从零开始 Windows 书籍推荐 人工智能 前端资源 后端资源 壁纸 开源项目 测试 论文
Copyright © 2018-2021 松鼠乐园. Designed by nicetheme. 浙ICP备15039601号-4
  • 重大新闻
  • Centos
  • CSS
  • Docker
  • ES5
  • ES6
  • Go
  • Java
  • Javascript
  • Linux
  • Mac
  • MySQL
  • Nginx
  • Redis
  • Springboot
  • Tensorflow
  • Vue
  • Vue2.x从零开始
  • Windows
  • 书籍推荐
  • 人工智能
  • 前端资源
  • 后端资源
  • 壁纸
  • 开源项目
  • 测试
  • 论文
热门搜索
  • jetson nano
  • vue
  • java
  • mysql
  • 人工智能
  • 人脸识别
迦娜王
坚持才有希望
1224 文章
33 评论
231 喜欢
  • 0
  • 0
  • Top