Observer

class Observer{
// Define attributes
  value;
  dep;
  vmCount;
// constructor
  constructor(value){
    this.value = value
    this.dep = new Dep()
    this.vmCount = 0
    // Mount an __ob__ attribute for value that instantiates an OB object
    def(value,'__ob__'.this)
    if(Array.isArray(value)){
      // To determine whether __proto__ is available, mount Array properties and methods to the value object
      if(hasProto){
        protoAugment(value,arrayMethods)
      }else{
        copyAugment(value,arrayMethods,arrayKeys)
      }
      this.observeArray(value)
    }else{
      this.walk(value)
    }
  }
  // Iterate over value to observe
  walk(obj){
    const keys = Object.keys(obj)
    for(let i = 0; i < keys.length; i ++){ defineReactive(obj,keys[i]) } }observeArray(items){
    for(let i = 0, l = items.length; i < l; i ++){
      observe(items[i])
    }
  }
}
Copy the code

observe

// Determine whether the current object is monitored, otherwise instantiate
export function observe(value,asRootData){
  if(! isObject(value) || valueinstanceof VNode){
    return 
  }
  let ob
  if(hasOwn(value,'__ob__') && value.__ob__ instanceof Observer){
    ob = value.__ob__
  }else if(shouldObserve && ! isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && ! value._isVue){ ob =new Observer(value)
  }
  if(asRootData && ob){
    ob.vmCount ++
  }
  return ob
}
Copy the code

defineReactive

export function defineReactive(obj,key,val,customSetter,shallow){
  const dep = new Dep()
  const property = Object.getOwnPropertyDescriptor(obj,key)
  if(property && property.configurable === false) {return 
  }
  const getter = property && property.get
  const setter = property && property.set
  // Get the key attribute of the default obj
  if((! getter || setter) &&arguments.length === 2 ){
    val = obj[key]
  }
  // Get val's Observe
  letchildOb = ! shallow && observe(val)// Redefine the read and write of key values
  Object.defineProperty(obj,key,{
    enumerable: true.configurable: true.get: function reactiveGetter(){
      const value = getter ? getter.call(obj) : val
      if(Dep.target){
        dep.depend()
        if(childOb){
          childOb.dep.depend()
          if(Array.isArray(value)){
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter(newVal){
      const value = getter ? getter.call(obj) : val
      if(newVal === value || (newVal ! == newVal && value ! == value)){return
      }
      if(process.env.NODE_ENV ! = ='production' && customSetter){
        customSetter()
      }
      if(getter && ! setter)return
      if(setter){
        setter.call(obj,newVal)
      }else{ val = newVal } childOb = ! shallow && observe(newVal) dep.notify() } }) }Copy the code

Dep

let uid = 0
export default class Dep{
  static target;
  id;
  subs;
  constructor(){
    this.id = uid ++
    this.subs = []
  }
  addSub(sub){
    this.subs.push(sub)
  }
  removeSub(sub){
    remove(this.subs,sub)
  }
  depend(){
    if(Dep.target){
      Dep.target.addDep(this)}}notify(){
    const subs = this.subs.slice()
    if(process.env.NODE_ENV ! = ='production' && !config.async){
      subs.sort((a,b) = > a.id - b.id)
    }
    for(let i = 0, l = subs.length; i < l; i++){
      subs[i].update()
    }
  }
}
Dep.target = null
const targetStatck = []
// Ensure that dep. target is always the latest one
function pushTarget(target){
  targetStack.push(target)
  Dep.target = target
}
function popTarget(){
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]}Copy the code

Watcher

class Watcher{
  vm,expression,cb,id,deep,user,lazy,sync,dirty,active,deps,newDeps,depIds,newDepIds,before,getter,value;
  
  constructor(vm,expOrFn,cb,options,isRenderWatcher){
    this.vm = vm
    // Mount _watchers to vm
    if(isRenderWatcher){
      vm._watcher = this
    }
    vm._watchers.push(this)
    if(options){
      this.deep = !! options.deepthis.user = !! options.userthis.lazy = !! options.lazythis.sync = !! options.syncthis.before = options.before
    }else{
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid
    this.active = true
    this.dirty = this.lazy
    this.deps = []
    this.newDeps = []
    this.depIds = new Set(a)this.newDepIds = new Set(a)this.expression = process.env.NODE_ENV ! = ='production' ? expOrFn.toString() : ' '
    if(typeof expOrFn === 'function') {this.getter = expOrFn
    }else{
      this.getter = parsePath(expOrFn)
    }
    this.value = this.lazy ? undefined : this.get()
  }

  get(){
    // Set dep. target and addDep when reading data
    pushTarget(this)
    let value
    const vm = this.vm
    try{
      value = this.getter.call(vm,vm)
    }catch(e){
      ...
    }finally{
      if(this.deep){
        traverse(value)
      }
      // Dep. Target is cleared and cleanupDeps is executed
      popTarget()
      this.cleanupDeps()
    }
    return value
  }

  addDep(dep){
    // Save to deP and save to Watcher
    const id = dep.id
    if(!this.newDepIds.has(id)){
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if(!this.depIds.has(id)){
        dep.addSub(this)}}}cleanupDeps(){
    // If the backup deP does not have the current DEP, delete the current DEP watcher permanently
    let i = this.deps.length
    while(i --){
      const dep = this.deps[i]
      if(!this.newDepIds.has(dep.id)){
        dep.removeSub(this)}}// Set depIds and deps as backups of newDepIds and newDeps
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  update(){
    // Determines whether the run-watch callback is executed immediately or the updated callback is executed
    if(this.lazy){
      this.dirty = true
    }else if(this.sync){
      this.run()
    }else{
      queueWatcher(this)}}run(){
    // Get newValue and call back
    if(this.active){
      const value = this.get()
      if(value ! = =this.value || isObject(value) || this.deep){
        const oldValue = this.value
        this.value = value
        if(this.user){
          try{
            this.cb.call(this.vm,value,oldValue)
          }catch(e){
            handleError(e,this.vm,`callback for watcher "The ${this.expression}"`)}}else{
          this.cb.call(this.vm,value,oldValue)
        }
      }
    }
  }

  evalute(){
    this.value = this.get()
    this.dirty = false
  }

  depend(){
    let i = this.deps.length
    while(i --){
      this.deps[i].depend()
    }
  }

  / / remove the watcher
  teardown(){
    if(this.active){
      if(!this.vm._isBeingDestroyed){
        remove(this.vm._watchers,this)}let i = this.deps.length
      while(i --){
        this.deps[i].removeSub(this)}this.active = false}}}Copy the code

Summary – Bidirectional binding

  • DefineReactive — Get and set are redefined with Object.defineProperty
    • Getter: Determines the get of the original property and adds watcher via deP
    • Setter: Judge setter, call watcher’s update via deP, call watch callback or update hook function; Also set childOb/Observer according to newVal, updated at GET