Vue responsive implementation principle

Object.defineProperty

grammar

Object.defineProperty(obj, prop, descriptor)
Copy the code

parameter

obj
Copy the code

The object for which attributes are to be defined.

prop
Copy the code

The name or Symbol of the property to be defined or modified.

descriptor
Copy the code

The property descriptor to define or modify.

Description of descriptor parameters

  • configurable

    The descriptor of this property can be changed and deleted from the corresponding object only when the property’s key is set to true. The default is false.

  • enumerable

    The property appears in the object’s enumerable property if and only if its Enumerable key value is true. The default is false.

  • value

    The value corresponding to this property. It can be any valid JavaScript value (numeric value, object, function, etc.). The default is false.

  • writable

    The property’s value, the value above, can be changed by the assignment operator if and only if the property’s writable key is true. The default is false.

  • get

    Property, or undefined if there is no getter. This function is called when the property is accessed. No arguments are passed, but this object is passed (because of inheritance, this is not necessarily the object that defines the property). The return value of this function is used as the value of the property. The default is undefined.

  • set

    Property, or undefined if there is no setter. This function is called when the property value is modified. This method takes an argument (that is, the new value being assigned) and passes in the this object at the time of assignment. The default is undefined.

Getter and setter

ES5’s Object.defineProperty provides the ability to listen for property changes, and I’ll show you how covert functions can print logs when modifying Object properties by modifying getters and setters of incoming objects.

const obj = { foo: 123 }
convert(obj) 
obj.foo // Need to print: 'Getting key "foo": 123'
obj.foo = 234 Setting key "foo" to 234'
obj.foo // Need to print: 'Getting key "foo": 234'
Copy the code

The Covert function is implemented as follows:

function convert (obj) {
  // object. keys Retrieves all the keys of the Object and modifies each property through forEach
  Object.keys(obj).forEach(key= > {
    // Save the initial value of the property
    let internalValue = obj[key]
    Object.defineProperty(obj, key, {
      get () {
        console.log(`getting key "${key}": ${internalValue}`)
        return internalValue
      },
      set (newValue) {
        console.log(`setting key "${key}" to: ${newValue}`)
        internalValue = newValue
      }
    })
  })
}
Copy the code

Dependency tracking (subscription publishing mode)

We need to implement a dependency tracking class Dep with a method called Depend that collects dependencies. There is also a notify method that triggers the execution of dependencies, that is, as long as the dependencies that were previously collected using the DEP method will be triggered when the Notfiy method is called.

Here is what the Dep class is expected to do. The dep. Depend method is called to collect dependencies. When dep.notify is called, the console prints its updated statement again

const dep = new Dep()

autorun(() = > {
  dep.depend()
  console.log('updated')})// Print: "updated"

dep.notify()
// Print: "updated"
Copy the code

The Autorun function receives a function that helps us create a response area in which dependencies can be registered via dep.depend

The final Dep class code is as follows:

window.Dep = class Dep {
  constructor () {
    // Subscribes to a queue of tasks in the same way as the Set data structure
    this.subscribers = new Set()}// Used to register dependencies
  depend () {
    if (activeUpdate) {
      this.subscribers.add(activeUpdate)
    }
  }
	// Used to publish messages that trigger dependency re-execution
  notify () {
    this.subscribers.forEach(sub= > sub())
  }
}

let activeUpdate = null

function autorun (update) {
  // Assign wrappedUpdate to activeUpdate, which causes the update function to be reexecuted when the dependency changes
  WrappedUpdate is actually called, and the dependency tracker will continue to collect dependencies if any changes are made later
  // Because the update function may contain conditions, collect this dependency if a variable is true and others if it is false
  // Therefore, the dependency collection system needs to update these dependencies dynamically to keep them up to date
  const wrappedUpdate = () = > {
    activeUpdate = wrappedUpdate
    update()
    activeUpdate = null
  }
  wrappedUpdate()
}
Copy the code

Implementing mini observer

We put the above two exercises together to implement a small observer that automatically updates data by calling the Depend and Notfiy methods in getter and setter, which is the core principle behind Vue’s automatic updates.

Expected call effect:

const state = {
  count: 0
}
/ / to monitor the state
observe(state)
// dependency injection
autorun(() = > {
  console.log(state.count)
})
// Print "count is: 0"
// Execute notfiy on each reassignment to iterate over all dependent functions
state.count++
// Print "count is: 1"
Copy the code

The final integration code is as follows:

class Dep {
  constructor () {
    this.subscribers = new Set()
  }

  depend () {
    if (activeUpdate) {
      this.subscribers.add(activeUpdate)
    }
  }

  notify () {
    this.subscribers.forEach(sub= > sub())
  }
}

function observe (obj) {
  Object.keys(obj).forEach(key= > {
    let internalValue = obj[key]

    const dep = new Dep()
    Object.defineProperty(obj, key, {
      // Collect dependencies in the getter and re-run when notify is triggered
      get () {
        dep.depend()
        return internalValue
      },

      // setters are used to call notify
      set (newVal) {
        constchanged = internalValue ! == newVal internalValue = newValif (changed) {
          dep.notify()
        }
      }
    })
  })
  return obj
}

let activeUpdate = null

function autorun (update) {
  const wrappedUpdate = () = > {
    activeUpdate = wrappedUpdate
    update()
    activeUpdate = null
  }
  wrappedUpdate()
}
Copy the code