This is the 12th day of my participation in the August More Text Challenge. For details, see:August is more challenging

The previous article showed that the {{value}} of the text node was successfully changed with variables inside the DVue. This article will pick up where we left off and implement some basic instructions, as well as a simple update operation.

Initialize instructions that you can see

In the case of vue, there may be some special directive notation on element nodes during compilation.

Here, take the V-text, V-HTML, @click, and V-Model directives as examples.

To compile the instruction, we need to process the byte attributes when compiling the child node. Get the attributes of the current node through the attributes, turn them into an array, and iterate over their respective attribute values and attribute names.

/ / the Compile classes
if (isNode(child)) {
  / / element
  // Parse dynamic instruction property binding, event listener
  const childAttrs = child.attributes
  Array.from(childAttrs).forEach((attr) = > {
    const attrName = attr.name
    const exp = attr.value
    if (this.isDir(attrName)) {
      console.log(exp, attrName) // name d-model age d-text html d-html
      const dir = attrName.slice(2)
      this[dir] && this[dir](child, exp)
    }
  })
  if (child.childNodes.length > 0) this.compiler(child)
}
function isDir(dir) {
  return dir.startsWith('d-')}Copy the code

To determine if the instruction starts with d- (isDir), cut its property name from the second place to get the function with the property name (e.g., d-text to get text), and execute it if it exists.

D-text, D-HTML implementation

implementation

Write HTML functions and text functions. Text function is to replace its internal text variable, can directly use the node’s textContent property, with the corresponding variable in the DVue to replace the old text content.

html(node, exp) {
  node.innerHTML = this.$vm[exp]
  console.log(node, exp)
  node.removeAttribute('d-html')}text(node, exp) {
  node.textContent = this.$vm[exp]
}
Copy the code

For HTML it is to add the variable value to the internal node of the current node as an HTML node, and then remove the corresponding attribute of the node using removeAttribute.

Further optimization

For common processing instruction functions, we can extract common initialization functions (update: for initialization and update).

  update(node, exp, dir) {
  / / initialization
    const fn = this[dir + 'Updater']
    fn && fn(node, this.$vm[exp])
    / / update
  }
  html(node, exp) {
    // node.innerHTML = this.$vm[exp]
    this.update(node, exp, 'html')
    node.removeAttribute('d-html')}htmlUpdater(node, val) {
    node.innerHTML = val
  }
Copy the code

Break it down into three methods for later update operations. At this point, our d-text, d-HTML is complete.

The update operation

Vue update, last time the classic graph:

This time, we need to complete the Watcher function. It is responsible for specific node updates. Initialize Watcher.

watcher

Use full update, do not use DEP management. Define an array of Watcher watchers, create watcher instances at compile time, and put them all into one Watcher. Traversal triggers updates when related variables change.

const watchers = []
// Responsible for specific node updates
class Watcher {
  constructor(vm, key, updater) {
    this.vm = vm
    this.key = key
    this.updater = updater
    watchers.push(this)}update() { // Update the corresponding key
    this.updater.call(this.vm, this.vm[this.key])
  }
}
function defineReactive(obj, key, val) {
  observe(val)
  Object.defineProperty(obj, key, {
   ......
    set(newVal) {
      if(newVal ! == val) val = newVal observe(newVal) watchers.forEach((w) = > w.update())  // Iterate over updates}})}Copy the code

You can see that the name in the figure changes with the action of the timer.

Dep

One – to – one correspondence between Dep and reactive attribute key. Use Dep to manage watcher. Initialize the DEP, which should contain an array to manage the watcher collection. And there are ways to collect watcher and trigger updates.

class Dep {
  constructor() {
    this.deps = []
  }
  addDep(dep) {
    this.deps.push(dep)
  }
  notify() {
    this.deps.forEach((w) = > w.update())
  }
}
Copy the code
  • When each variable is intercepted, the corresponding Dep is created.
  • When the watcher instance is created, the watcher is stored in a variable (target) with Dep. When the variable is read, the watcher is collected with Dep. After saving, delete the target attribute.
  • Dep’s update operation (update watcher) is triggered when the related variable changes.
function defineReactive(obj, key, val) {
  observe(val)
  const dep = new Dep()  // 1. Create an instance
  Object.defineProperty(obj, key, {
    get() {
      // console.log('get', key)
      Dep.target && dep.addDep(Dep.target)  // 2.2
      return val
    },
    set(newVal) {
      if(newVal ! == val) val = newVal observe(newVal)// watchers.forEach((w) => w.update())
      dep.notify()  // 3, trigger dependencies}})}class Watcher {
  constructor(vm, key, updater) {
    this.vm = vm
    this.key = key
    this.updater = updater
    // watchers.push(this)
    Dep.target = this  // 2, save watcher, read variables to collect dependencies
    this.vm[this.key]
    Dep.target = null
  }
  update() {
    this.updater.call(this.vm, this.vm[this.key])
  }
}
class Dep {
  constructor() {
    this.deps = []
  }
  addDep(dep) {
    this.deps.push(dep)
  }
  notify() {
    this.deps.forEach((w) = > w.update())
  }
}
Copy the code

Updated commands for easy viewing

Directives that are easier to view after the update, such as event listener @click, v-model.

@ click implementation

When compiling instructions dynamically, we also need to consider whether the current property has event listeners. If the current attribute is an event listener, then we need to add an addEventListener on the current node.

compiler(el) {
    const childNodes = el.childNodes
    childNodes.forEach((child) = > {
      if (isNode(child)) { / / element
        // Parse dynamic instruction property binding, event listener
        const childAttrs = child.attributes
        Array.from(childAttrs).forEach((attr) = > {
          const attrName = attr.name
          const exp = attr.value
         ...
          / / event
          if (this.isEvent(attrName)) {
            const dir = attrName.slice(1)
            this.eventHandler(child, exp, dir) // 
      
"add" "click"
}})... }... })}isEvent(name) { return name.indexOf(The '@') = =0 } eventHandler(node, exp, dir) { const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp] node.addEventListener(dir, fn.bind(this.$vm)) } Copy the code

In particular, it is important to note that when adding an event listener, you may need to use a variable or method in the current instance. You need to change the current reference to this and pass in the instance.

Achievement Display:

D – the model implementation

The Model directive is a bi-directional directive that needs to be implemented to display its value to the page, and its associated variables change when the input is modified internally. Can be converted to value Settings and event listening two functions.

The Model directive is similar to the HTML and text directives in that it is split into three methods, which share the same update method.

  model(node, exp) {
    this.update(node, exp, 'model')}modelUpdater(node, val) {
  // Form element assignment
    node.value = val
  }
Copy the code

Initialization is complete:

In input, you need to listen for the current event.

model(node, exp) {
  this.update(node, exp, 'model')
  // Event listening
  node.addEventListener('input'.(e) = > (this.$vm[exp] = e.target.value))
}
Copy the code

When the input box is typed, the input is reassigned to this variable. You can see that the value bound to the input box is associated with the associated variable.

If you’re interested, check out the Vue source code For a plump item for one item (●’ extract ‘●). If not enough, please advise.