vue 源码剖析之怎样完成 observer 和 watcher

本文能帮你做什么?
。。猎奇vue双向绑定的同砚,
能够部份减缓猎奇心
还能够帮你相识如何完成$watch

前情回忆

我之前写了一篇没什么干货的文章。。而且刨了一个大坑。。
本日。。打算来填一天。。并再刨一个。。哈哈
不过话说说回来了.看本文之前,,
假如不晓得Object.defineProperty,还必须看看剖析奇异的 Object.defineProperty
不能不慨叹vue的作者,人长得帅,码写的也好。
本文是依据作者源码,摘取出来的

本文将完成什么

正如上一篇许下的许诺一样,本文要完成一个 $wacth

const v = new Vue({
  data:{
    a:1,
    b:2
  }
})
v.$watch("a",()=>console.log("哈哈,$watch胜利"))
setTimeout(()=>{
  v.a = 5
},2000) //打印 哈哈,$watch胜利

为了协助人人理清思绪。。我们就做最简朴的完成。。只斟酌对象不斟酌数组

1. 完成 observer

思绪:我们晓得Object.defineProperty的特征了,
我们就利用它的set和get。。我们将要observe的对象,
经由过程递归,将它一切的属性,包含子属性的属性,都给加上set和get,
如许的话,给这个对象的某个属性赋值,就会触发set。。嗯。。最先吧

export default class  Observer{
  constructor(value) {
    this.value = value
    this.walk(value)
  }
  //递归。。让每一个字属性能够observe
  walk(value){
    Object.keys(value).forEach(key=>this.convert(key,value[key]))
  }
  convert(key, val){
    defineReactive(this.value, key, val)
  }
}


export function defineReactive (obj, key, val) {
  var childOb = observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: ()=>val,
    set:newVal=> {      
     childOb = observe(newVal)//假如新赋值的值是个庞杂范例。再递归它,加上set/get。。
     }
  })
}


export function observe (value, vm) {
  if (!value || typeof value !== 'object') {
    return
  }
  return new Observer(value)
}

代码很简朴,就给每一个属性(包含子属性)都加上get/set,
如许的话,这个对象的,有任何赋值,就会触发set要领。。
所以,我们是否是应当写一个音讯-定阅器呢?如许的话,
一触发set要领,我们就发一个关照出来,然后,定阅这个音讯的,
就会如何?。。。对咯。。收到音讯。。。触发还调。

2. 音讯-定阅器

很简朴,我们保护一个数组,,这个数组,就放定阅着,一旦触发notify,
定阅者就挪用本身的update要领

export default class Dep {
  constructor() {
    this.subs = []
  }
  addSub(sub){
    this.subs.push(sub)
  }
  notify(){
    this.subs.forEach(sub=>sub.update())
  }
}

所以,每次set函数,挪用的时刻,我们是否是应当,触发notify,对吧。所以
我们把代码补充完全

    export function defineReactive (obj, key, val) {
      var dep = new Dep()
      var childOb = observe(val)
      Object.defineProperty(obj, key, {
        enumerable: true,
        configurable: true,
        get: ()=>val,
        set:newVal=> {
          var value =  val
          if (newVal === value) {
            return
          }
          val = newVal
          childOb = observe(newVal)
          dep.notify()
        }
      })
    }

那末题目来了。。谁是定阅者。。对,是Watcher。。一旦 dep.notify()
就遍历定阅者,也就是Watcher,并挪用他的update()要领

3. 完成一个 Watcher

我们设想这个Watcher,应当用什么东西。update要领,嗯这个毋庸置疑,
另有呢,

    v.$watch("a",()=>console.log("哈哈,$watch胜利"))

对表达式(就是谁人“a”) 和 回调函数,这是最基本的,所以我们简朴写写

export default class Watcher {
  constructor(vm, expOrFn, cb) {
    this.cb = cb
    this.vm = vm
    //此处简化.要辨别fuction照样expression,只斟酌最简朴的expression
    this.expOrFn = expOrFn
    this.value = this.get()
  }
  update(){
    this.run()
  }
  run(){
    const  value = this.get()
    if(value !==this.value){
      this.value = value
      this.cb.call(this.vm)
    }
  }
  get(){
    //此处简化。。要辨别fuction照样expression
    const value = this.vm._data[this.expOrFn]
    return value
  }
}

那末题目来了,我们如何将经由过程addSub(),将Watcher加进去呢。
我们发明var dep = new Dep() 处于闭包当中,
我们又发明Watcher的组织函数里会挪用this.get
所以,我们能够在上面动动四肢,
修正一下Object.definePropertyget要挪用的函数,
推断是否是Watcher的组织函数挪用,假如是,申明他就是这个属性的定阅者
坚决将他addSub()中去,那题目来了,
我如何推断他是Watcherthis.get挪用的,而不是我们一般挪用的呢。
对,在Dep定义一个全局唯一的变量,随着思绪我们写一下

export default class Watcher {
  ....省略未修改代码....
  get(){
    Dep.target = this
    //此处简化。。要辨别fuction照样expression
    const value = this.vm._data[this.expOrFn]
    Dep.target = null
    return value
  }
}

如许的话,我们只需要在Object.definePropertyget要挪用的函数里,
推断有无值,就晓得究竟是Watcher 在get,照样我们本身在检察赋值,假如
是Watcher的话就addSub(),代码补充一下


export function defineReactive (obj, key, val) {
  var dep = new Dep()
  var childOb = observe(val)

  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: ()=>{
      // 申明这是watch 引发的
      if(Dep.target){
        dep.addSub(Dep.target)
      }
      return val
    },
    set:newVal=> {
      var value =  val
      if (newVal === value) {
        return
      }
      val = newVal
      childOb = observe(newVal)
      dep.notify()
    }
  })
}

末了不要遗忘,在Dep.js中加上这么一句

Dep.target = null

4. 完成一个 Vue

还差一步就功德圆满了,我们要把以上代码合营Vue的$watch要领来用,
要watch Vue实例的属性,算了,,不要剖析我在说什么,,直接看代码吧

import Watcher from '../watcher'
import {observe} from "../observer"

export default class Vue {
  constructor (options={}) {
    //这里简化了。。实在要merge
    this.$options=options
    //这里简化了。。实在要辨别的
    let data = this._data=this.$options.data
    Object.keys(data).forEach(key=>this._proxy(key))
    observe(data,this)
  }


  $watch(expOrFn, cb, options){
    new Watcher(this, expOrFn, cb)
  }

  _proxy(key) {
    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
      }
    })
  }
}

异常简朴。。两件事,observe本身的data,代办本身的data,
使接见本身的属性,就是接见子data的属性。。
停止到现在,在我们只斟酌最简朴情况下。。全部流程终究跑通了。。一定会有
许多bug,本文重要目标是展现全部工作流,协助读者明白。。
代码在https://github.com/georgebbbb…
我是一万个不想展现本身代码。。由于许多槽点,还请包涵

下一篇,有两个方向,将聊一聊如何完成双向绑定,或者是如何watch数组。
关于vue2.0的新文章
100行代码,明白和剖析vue2.0的相应式架构

    原文作者:杨川宝
    原文地址: https://segmentfault.com/a/1190000004384515
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞