vue-响应式系统

1 篇文章 0 订阅

vue作为一个前端框架,近两年非常的火,虽然它的社区不像react那样繁荣,但它配套的东西都有固定的

团队维护,用起来更方便。

它是MVVM模型的框架(不熟悉框架模型的同学可以看看阮一峰大神的博客,或者点这里),实现数据的

双向绑定,与其他框架相比vue非常的轻量级,另一个重要的特点就

是它的响应式系统。

用一张图来表示的话就是这样
这里写图片描述

归纳起来,Vue.js在这里主要做了三件事:

1.通过 Observer 对 data 做监听,并且提供了订阅某个数据项变化的能力。
2.把 template 编译成一段 document fragment,然后解析其中的 Directive,得到每一个 Directive 所依赖的数据项和update方法。
3.通过Watcher把上述两部分结合起来,即把Directive中的数据依赖通过Watcher订阅在对应数据的 Observer 的 Dep 上。当数据变化时,就会触发 Observer 的 Dep 上的 notify 方法通知对应的 Watcher 的 update,进而触发 Directive 的 update 方法来更新 DOM 视图,最后达到模型和视图关联起来。

详细一点
1.首先在vue初始化数据的时候,会调用 vm.initData 方法处理 data 选项,里面有个proxy 方法,proxy

方法主要通过 Object.defineProperty 的 getter 和 setter 方法实现了代理。在前面的例子中,我们调用

vm.times 就相当于访问了 vm.data.times。在initData的最后会调用Observer方法,它里面首先创建了一

个 Dep 对象实例,(Dep 类是一个简单的观察者模式的实现,里面有subs用来存储订阅其中的

watcher,还有depend和notify方法。还有Dep.target 表示当前正在计算的 Watcher)Observer底层还

会调用object.defineProperty方法,把所有数据转为getter和setter形式,进行数据劫持,当触发getter

时,会调用dep.depend方法把当前 Dep 的实例添加到当前正在计算的Watcher 的依赖中(订阅起来),

当触发setter时,会调用dep.notify方法遍历所有的订阅 Watcher,调用它们的 update 方法(发布)。

至此,vm 实例中给 data 对象添加 Observer 的过程就结束了。

2.指令解析。先把 template 编译成一段 document fragment,拿到 el 对象,通过compile(el, options)

进行指令解析,它在遍历节点时用的是树的常见的深度遍历方法,里面会对节点进行判断,不同的节点调

用不同的方法处理,根据指令的描述不同初始化不同的Directive 指令对象,到这一步,Vue.js 从解析模

板到生成 Directive 对象的步骤就完成了。

Directive 在初始化时还定义了 this.update 方法,并创建了 Watcher,把 this.update 方法作为

Watcher 的回调函数。这里把 Directive 和 Watcher 做了关联,当 Watcher 观察到指令表达式值变化

时,会调用 Directive 实例的 _update 方法,最终调用指令的update方法更新 DOM 节点。

3.watcher。Directive 实例在初始化 Watcher时,会传入指令的 expression,watcher会对 expression

做进一步的解析,在 Watcher 构造函数的最后调用了 this.get 方法,进行收集依赖关系,在get方法内部

首先执行beforeGet方法,设置 Dep.target 为当前 Watcher 实例,为接下来的依赖收集做准备,在

beforeGet之后执行 this.getter.call(scope, scope),这个方法实际上相当于获取vm上的属性,这样就触

发了对象的 getter,接下来就比较熟悉了,调用 dep.depend() 方法完成依赖收集。它实际上是执行了

Dep.target.addDep(this),相当于执行了 Watcher 实例的 addDep 方法,把 Dep 实例添加到 Watcher

实例的依赖中,Watcher.prototype.addDep 方法就是把 dep 添加到 Watcher 实例的依赖中,同时又通

过 dep.addSub(this) 把 Watcher 实例添加到 dep 的订阅者中。

至此,指令完成了依赖收集,并且通过 Watcher 完成了对数据变化的订阅。

简单实现
大家可以去看看源码

用最简单,最简略的代码实现一下,写的很简略,有些地方可能没考虑,有更好的可以互相交流~

function VNode(tag, data, children, text) {
  return {
    tag: tag,
    data: data,
    children: children,
    text: text
  }
}

class Vue {
  constructor(options) {
    this.$options = options
    this._data = options.data
    Object.keys(options.data).forEach(key => this._proxy(key))
    observer(options.data)
    const vdom = watch(this, this._render.bind(this), this._update.bind(this))
    console.log(vdom)
  }
  _proxy(key) {
    const self = this
    Object.defineProperty(self, key, {
      configurable: true,
      enumerable: true,
      get: function proxyGetter () {
        return self._data[key]
      },
      set: function proxySetter (val) {
        self._data.text = val
      }
    })
  }
  _update() {
    console.log("我需要更新");
    const vdom = this._render.call(this)
    console.log(vdom);
  }
  _render() {
    return this.$options.render.call(this)
  }
  __h__(tag, attr, children) {
    return VNode(tag, attr, children.map((child)=>{
      if(typeof child === 'string'){
        return VNode(undefined, undefined, undefined, child)
      }else{
        return child
      }
    }))
  }
  __toString__(val) {
    return val == null ? '' : typeof val === 'object' ? JSON.stringify(val, null, 2) : String(val);
  }
}

function observer(value, cb){
  Object.keys(value).forEach((key) => defineReactive(value, key, value[key] , cb))
}

function defineReactive(obj, key, val, cb) {
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: ()=>{
      if(Dep.target){
        dep.add(Dep.target)
      }
      return val
    },
    set: newVal => {
      if(newVal === val)
        return
      val = newVal
      dep.notify()
    }
  })
}
function watch(vm, exp, cb){
  Dep.target = cb
  return exp()
}

class Dep {
  constructor() {
    this.subs = []
  }
  add(cb) {
    this.subs.push(cb)
  }
  notify() {
    this.subs.forEach((cb) => cb())
  }
}
Dep.target = null


var demo = new Vue({
  el: '#demo',
  data: {
    text: "before",
  },
  render(){
    return this.__h__('div', {}, [
      this.__h__('span', {}, [this.__toString__(this.text)])
    ])
  }
})


setTimeout(function(){
  demo.text = "after"
}, 3000)
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值