Data driven

  • Data response
    • The data model is just a normal JavaScript object, and as we modify the data, the view is updated, eliminating tedious DOM manipulation and improving development efficiency
  • Two-way binding
    • Data changes, view changes; As the view changes, so does the data
    • We can usev-modelCreate two-way data binding on form elements
  • Data-driven is one of the most unique features of Vue
    • You need to focus on the data itself, not how the data is rendered to the view. Okay

Data response principle Vue2

Cn.vuejs.org/v2/guide/re…

Data response principle Vue3

  • Developer.mozilla.org/zh-CN/docs/…
  • From zero to achieve Vue3.0 reactive source www.bilibili.com/video/BV1AQ…

Observer pattern and subscriber publisher pattern

Publish subscribe pattern – publish-subscribe pattern

Subscriber, publisher, signal center

// Simple implementation
// Event trigger
    class EventEmitter {
      constructor () {
        // { 'click': [fn1, fn2], 'change': [fn] }
        this.subs = Object.create(null)}// Register events
      $on (eventType, handler) {
        this.subs[eventType] = this.subs[eventType] || []
        this.subs[eventType].push(handler)
      }

      // Trigger the event
      $emit (eventType) {
        if (this.subs[eventType]) {
          this.subs[eventType].forEach(handler= > {
            handler()
          })
        }
      }
    }

    / / test
    let em = new EventEmitter()
    em.$on('click'.() = > {
      console.log('click1')
    })
    em.$on('click'.() = > {
      console.log('click2')
    })

    em.$emit('click')
Copy the code

Observer model

  • Observer (Subscriber) – Watcher
    • Update (): Exactly what to do when an event occurs
  • Target (publisher) -dep
    • Subs array: Stores all observers
    • AddSub (): Adds an observer
    • Notify (): When an event occurs, the update() method of all observers is called
  • No event center
// Publisher - target
    class Dep {
      constructor () {
        // Record all subscribers
        this.subs = []
      }
      // Add subscribers
      addSub (sub) {
        if (sub && sub.update) {
          this.subs.push(sub)
        }
      }
      // Issue a notification
      notify () {
        this.subs.forEach(sub= > {
          sub.update()
        })
      }
    }
    // Subscriber - observer
    class Watcher {
      update () {
        console.log('update')}}/ / test
    let dep = new Dep()
    let watcher = new Watcher()

    dep.addSub(watcher)

    dep.notify()
Copy the code

parsing

vue.js

* Function * is responsible for receiving initialized parameters (options) * Is responsible for injecting properties from 'data' into 'Vue' instances, Convert to 'getter/setter' * is responsible for calling 'observer' to listen for changes to all properties in 'data' * is responsible for calling 'Compiler' to parse index/difference expressions * structure * properties * $options * $EL * $data * _proxyData()Copy the code

observer.js

* Function * is responsible for converting properties in the 'data' option into responsive data. * An attribute in the 'data' option is also an object. Convert this property to reactive data * data change send notification * Structure * Property * None * Method * walk(data) * defineReactive(data, key, value)Copy the code


class Observer {
    
    constructor(data) {
        this.walk(data)
    }

    walk(data) {
        // 1. Check whether data is an object
        if(! data ||typeofdata ! ='object') {
            return
        }
        // 2. Iterate over all properties of the data object
        Object.keys(data).forEach(key= > {
            this.defineReactive(data, key, data[key])
        })
    }

    defineReactive(obj, key, val) {
        let that = this
        // If val is an object, convert the attributes inside val to responsive data
        this.walk(val)
        Object.defineProperty(obj, key, {
            enumerable: true.configurable: true,
            get () {
                return val
            },
            set (newValue) {
                if (newValue == val) {
                    return
                }
                val = newValue
                that.walk(newValue)
                // Send notifications}}}})Copy the code

compiler.js

  • function
    • Responsible for compiling templates and parsing instruction/differential expressions
    • Responsible for the first rendering of the page
    • Re-render the view when the data changes
  • structure
    • attribute
      • el
      • vm
    • methods
      • compile(el)
      • compileElement(node)
      • compileText(node)
      • isDirective(attrName)
      • isTextNode(node)
      • isElementNode(node)
class Compiler {
  constructor(vm) {
    this.el = vm.$el
    this.vm = vm
    this.compile(this.el)
  }
  // Compile templates to handle text nodes and element nodes
  compile(el) {
    let childNodes = el.childNodes
    Array.from(childNodes).forEach(node= > {
      // Process text nodes
      if (this.isTextNode(node)) {
        this.compileText(node)
      } else if (this.isElementNode(node)) {
        // Process element nodes
        this.compileElement(node)
      }
      if (node.childNodes && node.childNodes.length) {
        this.compile(node)
      }
    })
  }
  // Compile element node, process instruction
  compileElement(node) {
    // Iterate over all attribute nodes
    Array.from(node.attributes).forEach(attr= > {
      // Check if it is a command
      let attrName = attr.name
      if (this.isDirective(attrName)) {
        // 将 v-text --> text
        attrName = attrName.substr(2)
        let key = attr.value
        this.update(node, key, attrName)
      }
    })

  }

  update(node, key, attrName) {
    let updateFn = this[attrName + 'Updater']
    updateFn && updateFn(node, this.vm[key])
  }

  // Process the V-text instruction
  textUpdater(node, value) {
    node.textContent = value
  }
  / / processing v - model
  modelUpdater(node, value) {
    node.value = value
  } 

  // Compile the text node to handle the difference expression
  compileText(node) {
    let reg = / \ {\ {(. +?) \} \} /
    let value = node.textContent
    if (reg.test(value)) {
      let key = RegExp.$1.trim()
      node.textContent = value.replace(reg, this.vm[key])
    }
  }
  // Determine if element attributes are directives
  isDirective(attrName) {
    return attrName.startsWith('v-')}// Determine whether the node is a text node
  isTextNode(node) {
    return node.nodeType == 3
  }
  // Determine if the node is an element node
  isElementNode(node) {
    return node.nodeType == 1}}Copy the code

dep.js

class Dep {
  constructor() {
    // Store all observers
    this.subs = []
  }

  // Add an observer
  addSub(sub) {
    if (sub && sub.update) {
      this.subs.push(sub)
    }
  }

  // Send notifications
  notify() {
    this.subs.forEach(sub= > {
      sub.update()
    })
  }
}


// observer.js
defineReactive(obj, key, val) {
    let that = this
    // Collect dependencies and send notifications
    let dep = new Dep()
    // ...
    Object.defineProperty(obj, key, {
      get() {
        // ...
        // Collect dependencies
        Dep.target && Dep.addSub(Dep.target)
        return val
      },
      set(newValue) {
        // ...
        // Send notifications
        dep.notify()
      }
    })
  }
Copy the code

watcher.js


class Watcher {
  constructor(vm, key, cb) {
    this.vm = vm;
    // Key is the attribute name in data
    this.key = key;
    // The callback is responsible for updating the view
    this.cb = cb

    // Record the watcher object to the Dep static property target
    Dep.target = this
    // Trigger the get method, in which addSub is called

    this.oldValue = vm[key]
    Dep.target = null
  }

  // Update the view when data changes
  update () {
    let newValue = this.vm[this.key]
    if (this.oldValue == newValue) {
      return
    }
    this.cb(newValue)
  }
}
Copy the code

Watcher’s collection needs to be in compiler

  • compileText
// Compile the text node to handle the difference expression
compileText(node) {
    let reg = / \ {\ {(. +?) \} \} /
    let value = node.textContent
    if (reg.test(value)) {
      let key = RegExp.$1.trim()
      node.textContent = value.replace(reg, this.vm[key])

      / / create a watcher
      new Watcher(this.vm, key, (newVal) = > {
        node.textContent = newVal
      })
    }
  }
Copy the code
  • textUpdatermodelUpdater
// Process the V-text instruction
  textUpdater(node, value, key) {
    node.textContent = value
    new Watcher(this.vm, key, (newValue) = > {
      node.textContent = newValue
    })
  }
  / / processing v - model
  modelUpdater(node, value, key) {
    node.value = value
    new Watcher(this.vm, key, (newValue) = > {
      node.value = newValue
    })
  } 
Copy the code

Two-way binding

/ / processing v - model
  modelUpdater(node, value, key) {
    node.value = value
    // ...
    // Bidirectional binding
    node.addEventListener('input'.() = > {
      this.vm[key] = node.value
    })
  } 
Copy the code

conclusion