SRC > core > observer > index.js

$watch

$Watch is a encapsulation of Watcher

Vue.prototype.$watch = function (
    expOrFn: string | Function, cb: any, options? :Object
  ) :Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    const watcher = new Watcher(vm, expOrFn, cb, options) // Call new Watcher to implement basic functions of vm.$watch
    if (options.immediate) { // Check whether the immediate parameter is used. If yes, run the CB command immediately
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    return function unwatchFn () { // Cancel the observed data
      watcher.teardown() // The essence of this is to remove the Watcher instance from the dependency list of the currently being observed state}}Copy the code

The internal principle of Watch

Wathcer keeps track of who it subscribes to, which DEPs watcher instances are collected into, and then, when Watcher doesn’t want to subscribe to any more dePs, loops through its subscription list to tell them (DEPs) to remove it from their dependency list

export default class Watcher {
    constructor (vm, expOrFn, cb) {
        this.vm = vm
        this.deps = []
        this.depIds = new Set(a)this.getter = parsePath(expOrFn)
        this.cb = cb
        this.value = this.get()
    }
    ......
    addDep (dep) {
        const id = dep.id
        if (!this.depIds.has(id)) { // depIds indicates that the current Watcher has subscribed to the Dep
            this.depIds.add(id) // Record that the current Watcher has subscribed to this Dep
            this.deps.push(dep) // Keep track of which DEPs you subscribe to
            dep.addSub(this) // Subscribe yourself to Dep}}... }Copy the code

With the addDep method added to Watcher, the logic for collecting dependencies in Dep also needs to change:

let uid = 0

export default class Dep {
    constructor () {
        this.id = uid++
        this.subs = []
    }
    ......
    
    depend () {
        if (window.target) {
            window.target.addDep(this)}}... }Copy the code

The Dep records which Watchers need to be notified when data changes, and the Watcher also records which DEPs it is notified by.

Once we have logged which DEPs we subscribe to in Watcher, we can add a teardown method in Watcher to notify those subscribed DEPs to remove them from the dependency list

/** * removes itself from the Dep list of all dependencies */
teardown () {
    let i = this.deps.length
    while (i--) { // Loop through the Deps list and remove themselves by removeSub respectively
        this.deps[i].removeSub(this)}}Copy the code
export default class Dep {... removeSub (sub) {const index = this.subs.indexOf(sub)
        if (index > -1) {
            return this.subs.splice(index, 1)}}}Copy the code

Unwatche loops through Watcher’s subscription list, executing the removeSub method separately, where you remove yourself from the subs(subscription list)

Implementation principle of the deep parameter

In addition to triggering the dependency collection logic for the data being listened on, deep must also trigger the dependency collection logic for all subvalues of the value being listened on

export default class Watcher {
    constructor (vm, expOrFn, cb, options) {
        this.vm = vm
        
        // Add new code
        if (options) {
            this.deep = !! options.deep }else {
            this.deep = false
        }
        
        this.deps  = []
        this.depIds = new Set(a)this.getter = parsePath(expOrFn)
        this.cb = cb
        this.value = this.get()
        
    }
    
    get () {
        window.target = this
        let value = this.getter.call(vm, vm)
        
        // Add new code
        if (this.deep) {
            traverse(value) // deep core method, called before window.target = undefined
        }
        window.target = undefined
        return value
    }
}
Copy the code

Next, we recurse all the children of value to trigger their collection dependencies:

const seenObjects = new Set(a)export function traverse (val) {
    _traverse(val, seenObjects)
    seenObjects.clear()
}

function _traverse (val, seen) {
    let i, keys
    const isA = Array.isArray(val)
    Return if the object is not an array, an object, or a frozen object
    if((! isA && ! isObject(val)) ||Object.isFrozen(val)) {
        return
    }
    
    if (val.__ob__) { // Get dep.id, which is used to ensure that dependencies are not collected twice
        const depId = val.__ob__.dep.id
        if (seen.has(depId)) {
            return
        }
        seen.add(depId)
    }
    
    if (isA) { // If it is an array, loop through the array, recursively calling _traverse on each item of the array
        i = val.length
        while(i--) _traverse(val[i], seen)
    } else { // In the case of Object, loop through all keys in Object, perform a read operation, and recurse the subvalues
        keys = Object.keys(val)
        i = keys.length
        while(i--) _traverse(val[keys[i]], seen)
    }
}
Copy the code

$set

export function set (target: Array<any> | Object, key: any, val: any) :any {
  if(process.env.NODE_ENV ! = ='production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)}/** * if target is an array and key is a valid index, * If the index Key passed is greater than target.length then target.length is equal to the index passed in. * Use splice to set the val passed in to the position specified by target (the position of the index passed in). * When we set val to target using splice, the array interceptor detects that the target has changed, automatically converts val to reactive, and returns val */
  if (Array.isArray(target) && isValidArrayIndex(key)) { // Target is an array
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  
  /** * If the key is present in the target, the value has been detected */ In this case, you can modify the data by using the key and val
  if (key intarget && ! (keyin Object.prototype)) {
    target[key] = val
    return val
  }
  
  
  const ob = (target: any).__ob__ // Get target's __ob__ attribute
  if (target._isVue || (ob && ob.vmCount)) { // Avoid using $set on Vue instances or $data root data objectsprocess.env.NODE_ENV ! = ='production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  if(! ob) {// If target is not reactive, just set the new value with key and val
    target[key] = val
    return val
  }
  // If target is reactive, this situation requires tracking the change to the new property, which is converted to a getter/setter using defineReactive
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}
Copy the code

$delete

When a property is deleted, a message is automatically sent to the dependency notifying Watcher that the data has changed

import { del } from '.. /observer/index'
Vue.prototype.$delete = del
Copy the code

The above code mounts the $DELETE method on Vue’s prototype object

export function del (target, key) {
    const ob = target.__ob__
    delete target[key] // Remove the key from the target first, then send a message to the dependency
    ob.dep.notify()
}

Copy the code

Next, deal with the array case

export function del (target, key) {
    if (Array.isArray(target) && isValidArrayIndex(key)) {
        target.splice(key ,1)
        return
    }
    const ob = target.__ob__
    delete target[key]
    ob.dep.notify()
}

Copy the code

In the case of arrays, use splice to delete the element at the specified location. With the splice method, you no longer need to call notify() to send a message to the dependency because the array interceptor automatically sends notifications to the dependency

As with vm.set, vm.set, vm.set, vm.delete cannot be used on Vue instances or root data objects