I.MVVM framework three elements: data responsiveness, template engine and its rendering

1. Data responsiveness: monitor data changes and update in an attempt

  1. object.defineProperty
  2. proxy

2. Template engine: Provides template syntax for describing views

  1. The interpolation: {{}}
  2. Instructions: V-bind, V-ON, V-model, V-for, V-if

3. Render: How do I convert templates to HTML

  1. Template => vdom => dom
 vue add vuex
Copy the code

2. Principle of data response

  1. Simple implementation
const obj = {}

function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    get() {
      console.log(`get ${key}:${val}`);
      return val
    },
    set(newVal) {
      if(newVal ! == val) { console.log(`set ${key}:${newVal}`);
        val = newVal
      }
    }
  })
}

defineReactive(obj, 'foo'.'foo')
obj.foo
obj.foo = 'foooooooooooo'
Copy the code
  1. Combined with the view
<! DOCTYPE html> <html lang="en">
<head></head>
<body>
<div id="app"></div>
<script>
  const obj = {}
  function defineReactive(obj, key, val) {
    Object.defineProperty(obj, key, {
        get() {
          console.log(`get ${key}:${val}`);
          return val
        },
        set(newVal) {
          if(newVal ! == val) { val = newVal update() } } } ) } defineReactive(obj,'foo'.' ')
  
  obj.foo = new Date().toLocaleTimeString()
  
  function update() {
    app.innerText = obj.foo
  }
  
  setInterval(() => {
    obj.foo = new Date().toLocaleTimeString()
  }, 1000);
  
</script>
</body>
</html>
Copy the code
  1. Iterate over objects that need to be reactive
// Object responsivity: iterates over each key and defines getters and settersfunctionObserve (obj) {// Check that the obj type must be an objectif(typeof obj ! = ='object' || obj == null) {
    return
  }

  Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key]))
}
Copy the code
  1. Resolve nested objects
function defineReactive(obj, key, val) {
  observe(val)
  Object.defineProperty(obj, key, {
//...
Copy the code
  1. Resolve the case where the assigned value is an object
obj.baz = {a:1}
obj.baz.a = 10 // no ok
set(newVal) {
 if(newVal ! == val) { console.log('set', newVal); Observe (newVal) val = newVal}}Copy the code
  1. New attributes cannot be detected if added/removed
obj.dong = 'dong'Obj. dong // does not have a get message // write onesetMethod and the data to do reactive processingfunction set(obj, key, val) {defineReactive(obj, key, val)set(obj, 'dong'.'dong')
obj.dong

Copy the code

Responsivity of data applied to VUE

The principle of analysis

  1. NewVue () performs initialization first, performing reactive processing on data, which occurs in Observe
  2. At the same time, the template is compiled, the dynamically bound data is found, the view is obtained from the data and initialized, this process happens in compile
  3. Define both an update function and watcher, which watcher will call in the future when the data changes
  4. Because keys in date can be called multiple times in a view, each key requires a butler to pipe multiple Watcher
  5. Once the data changes in the future, the corresponding Dep will be found first and all Watcher will be informed to perform the update function

Involved Types

  1. Vue: frame constructor
  2. Observer: Perform data reactivity (here you need to distinguish between arrays and objects)
  3. Compile: Compile templates, initialize views, rely on collections (update function, created by Watcher)
  4. Watcher: Execute update function (update DOM)
  5. Dep: Manage multiple Watcher, batch update

The source code to achieve

Step 1: Frame constructor: Perform initialization

  1. Create a VUE instance
Class Vue {constructor(options) {// Save this option.$options = options

    this.$dataOptions. data // Respond to observe(this).$data// proxy proxy(this) // new Compile('#app', this)
  }
}
Copy the code
  1. Perform initialization to perform reactive processing on data
// Object responsive processingfunctionObserve (obj) {// Check that the obj type must be an objectif(typeof obj ! = ='object' || obj == null) {
    return} // For each responsive object, New Observer(obj)} constructor(value) {this.value = value // This.walk (value)} walk(obj) {object.keys (obj).foreach (key => defineReactive(obj, key, obj[key]))}}functionDefineReactive (obj, key, val) {// Do the same}Copy the code
  1. Proxy for $data
/ / will be$dataKey in to the KVue instancefunction proxy(vm) {
  Object.keys(vm.$data).forEach(key => {
    Object.defineProperty(vm, key, {
      get() {
        return vm.$data[key]
      },
      set(v) {
        vm.$data[key] = v
      }
    })
  })
}
Copy the code

Step 2: compile –compile

  1. Create an instance of compile
class Compile {
  constructor(el, vm) {
    this.$vm = vm

    this.$el= document.querySelector(el) // Compile the templateif (this.$el) {
      this.compile(this.$el}} // compile(el) {el.childnodes. ForEach (node => {// determine its typeif (this.isElement(node)) {
        // console.log('Compile element', node.nodeName);
        this.compileElement(node)
      } else if (this.isInter(node)) {
        // console.log('Compile interpolation', node.textContent);
        this.compileText(node)
      }

      if(node.childNodes) {this.compile(node)}})} // Interpolate text to compileText(node) {// get matching expression // node.textContent = this.$vm[RegExp.The $1]
    this.update(node, RegExp.The $1.'text')} // Get node attributes compileElement(node) {const nodeAttrs = node.attributes array. from(nodeAttrs).foreach (attr => {// v-xxx="aaa"Const attrName = attr.name // v-xxx const exp = attr.value // aaa // Determine the attribute typeif(this.isdirective (attrName)) {const dir = attrname.substring (2) // This [dir] && this[dir](node, exp)}else if(attrName.startWith(The '@')) {/ /}})} / / text text instructions (node, exp) {enclosing the update (node, exp.'text')
  }

  html(node, exp) {

    this.update(node, exp, 'html'Update (node, exp, dir) {// textUpdater() // initialize const fn = this[dir +'Updater']
    fn && fn(node, this.$vm// Update: new Watcher(this).$vm, exp, function(val) { fn && fn(node, val) }) } textUpdater(node, value) { node.textContent = value } htmlUpdater(node, Value) {node.innerhtml = value} // element isElement(node) {returnNode.nodetype === 1} {{xx}} isInter(node) {return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
  }

  isDirective(attrName) {
    return attrName.indexOf('v-') === 0}}Copy the code

Step 3: Rely on collection

A view uses a key from data, which is called a dependency. The same key may appear multiple times, and each time they need to be collected and maintained with a Watcher, a process called dependency collection. Multiple Watchers require a Dep to manage and are notified uniformly when updates are needed.

  1. Create a Dep instance for each key when defineReactive
  2. Initialize the view to read a key, such as name1, and create a watcher1
  3. Since the getter method for Name1 is triggered, watcher1 is added to the Dep corresponding to Name1
  4. When name1 updates and the setter fires, it can be notified through the corresponding Dep to manage all Watcher updates

Code parsing

  1. Create a Watcher
// Watcher: A dependency in the interface corresponds to a Watcher class Watcher {constructor(VM, key, {this.vm = vm this.key = key this.updateFn = updateFn; Trigger get() dep.target = this this.vm[this.key] dep.target = null} // butler call in defineReactiveupdateCall (this.vm, this.vm[this.key])}} () {this.updatefn. call(this.vm, this.vm[this.key])}}Copy the code
  1. Write the update function and instantiate Watcher
// Call the update function to interpolate text to compileText(node) {// console.log(RegExp).The $1);
  // node.textContent = this.$vm[RegExp.The $1];
  this.update(node, RegExp.The $1.'text')
}
text(node, exp) {
  this.update(node, exp, 'text')
}
html(node, exp) {
  this.update(node, exp, 'html')
}
update(node, exp, dir) {
  const fn = this[dir + 'Updater']
  fn && fn(node, this.$vm[exp])
  new Watcher(this.$vm, exp, function (val) {
    fn && fn(node, val)
  })
}
textUpdater(node, val) {
  node.textContent = val;
}
htmlUpdater(node, val) {
  node.innerHTML = val
}
Copy the code
  1. The statement Dep
 
class Dep {
  constructor() {
    this.deps = []
  }

  addDep(dep) {
    this.deps.push(dep)
  }

  notify() { this.deps.forEach(dep => dep.update()); }}Copy the code
  1. Trigger getter when creating watcher
Class constructor {constructor(vm, key, updateFn) { Get () dep.target = this this.vm[this.key] dep.target = null}}Copy the code
  1. Rely on the collection to create Dep instances
defineReactive(obj, key, val){
  this.observe(val);
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    get() {
      Dep.target && dep.addDep(Dep.target);
      return val

    },
    set(newVal) {
      if (newVal === val) return
      dep.notify()
    }
  })
}
Copy the code