Principle 1.

1.1 Define Active Simple version

Implement the response to the data using Object.defineProperty(), noting that the value needs to be stored in a temp


let temp; 
let obj = {a:1}
defineReactive(obj,"a")
function defineReactive(obj,key) {
    Object.defineProperty(obj,key,{
        get () {
            console.log("Call get method temp",temp)
            // console.log("obj[key]",obj[key]
            return temp
        },
        set (val) {
            console.log("Call set, val",val)
            temp = val
            return 
        }
    })
}

obj.a
obj.a = 2
obj.a

// Call get, temp undefined
// call set, val 2
// Call get, temp 2
Copy the code

1.2 defineReactive closure version

The method parameters are returned to the method body, forming a closure

let obj = {}
defineReactive(obj,"a".'1')
function defineReactive(obj,key,val) {
    Object.defineProperty(obj,key,{
        get () {
            console.log("Call the get method, val",val) 
            return val // Return the argument val directly to form a closure
        },
        set (newVal) {
            console.log("Call the set method, newVal",newVal)
            if(val === newVal) {
                return
            }
            val = newVal
            return 
        }
    })
}

obj.a
obj.a = 2
obj.a

Call the get method, val 1
// call the set method newVal 2
Call the get method, val 2
Copy the code

1.3. Simple page responses

Simple HTML timed automatic data response refresh

<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta  name="viewport" content="width=device-width, "> <title>Document</title> </head> <body> <div id="app"> </div> <script> let obj = {} defineReactive(obj,"a",'1') function defineReactive(obj,key,val) { Object.defineProperty(obj,key,{ get () { Console. log(" Call the get method, Set (newVal) {if(val === newVal) {return} console.log(" call the set method, newVal",newVal) val = newVal updateFn() } }) } let app = document.getElementById("app") function updateFn() { app.innerHTML = obj.a } setInterval(() => { obj.a = new Date().toLocaleTimeString(); }, 1000); </script> </body> </html>Copy the code

1.4. Iterate over all properties

// Observe the method
function observe(obj) {
    if (typeofobj ! = ="object" || obj == null) {
        return 
    }
    Object.keys(obj).forEach( k= > { 
        defineReactive(obj,key,obj[key])
    })
}
Copy the code

1.5. Iterate over all properties

// Observe the method
function observe(obj) {
    if (typeofobj ! = ="object" || obj == null) {
        return 
    }
    Object.keys(obj).forEach( key= > { 
        defineReactive(obj,key,obj[key])
    })
}
Copy the code

1.6. Solve the nesting object problem

function defineReactive(obj, key, val) {
    observe(val) // Determine if the value is also a recursive call to the object
}
Copy the code

1.7. Insert values are handled by objects

set(newVal) {
    if(newVal ! == val) { observe(newVal)// also called when assigning. }}// Vue's set method does this
function set(obj, key, val) {
    defineReactive(obj, key, val) 
}
Copy the code

2. The actual combat

2.1 Official VUE analysis

<meta charset="UTF-8">
<div id="app">
  <p>{{counter}}</p>
  <p j-text="counter"></p>
  <p j-html="desc"></p>
</div> 
<script src=".. /node_modules/vue/dist/vue.js"></script><! --<script src="./MyVue.js"></script> -->
<script>
  const app = new MyVue({
    el:'#app'.data: {
      counter: 1.desc: '<span style="color:red">123</span>'}})setInterval(() = > {
    app.counter++
  }, 1000);
</script>
Copy the code
  1. The data data response needs to be implemented
  2. Implement {{}} symbol resolution
  3. J -text and J – HTML tag parsing

2.2 Code Implementation

  1. Create a MyVue class that accepts initialization parameters
  2. Implement an OBSERVE method to handle the data response and the Observe class to handle the data type
  3. Create the Compile class to parse the special {{}} and j- tags
  4. Implement this.$data.a is equivalent to the data proxy for this

2.2.1 Observe Process

2.3Compile parsing process

Breadth traversal, depth first

  1. Iterate over all DOM nodes under the current root node to determine whether the current node is a {{}} or j- or @ event that needs to be resolved
  2. If the node continues deep recursive parsing, if not continue compile() parsing
  3. Use the re to match the execution type, such as the corresponding compileText method, and call the generic Update method to create new Watch and Dep
  4. Since the corresponding Watch and Dep are added to the key, the set method can be triggered to automatically call Dep to distribute notify updates of all watches when the key content changes next time


// Implement the KVue constructor
function defineReactive(obj, key, val) {
  // If val is an object, it needs to be processed recursively
  observe(val)

  // Create the butler
  const dep = new Dep()
  
  Object.defineProperty(obj, key, {
    get() {
      console.log('get', key);
      // Rely on collection
      Dep.target && dep.addDep(Dep.target)
      return val
    },
    set(newVal) {
      if(val ! == newVal) {// If newVal is an object, it is also reactive
        observe(newVal)
        val = newVal
        console.log('set', key, newVal);

        // Notification update
        dep.notify()
      }
    }
  })
}

// Iterate over each key of the specified data object, intercepting them
function observe(obj) {
  if (typeofobj ! = ='object' || obj === null) {
    return obj
  }

  // Create an Observer instance for each object encountered
  // Create an Observer instance to intercept
  new Observer(obj)
}

// proxy: allows users to directly access keys in data
function proxy(vm, key) {
  Object.keys(vm[key]).forEach(k= > {
    Object.defineProperty(vm, k, {
      get() {
        return vm[key][k]
      },
      set(v) {
        vm[key][k] = v
      }
    })
  })
}

// Do different operations depending on the type of value passed in
class Observer {
  constructor(value) {
    this.value = value

    // Determine the value type
    // Iterate over the object
    this.walk(value)
  }
  // Currently, only object cases are handled
  walk(obj) {
    Object.keys(obj).forEach(key= > {
      defineReactive(obj, key, obj[key])
    })
  }
}

class MyVue {
  constructor(options) {
    // 0. Save options
    this.$options = options
    this.$data = options.data
    // 1. Process data as a response
    observe(this.$data)
    // 2. Proxy for $data
    proxy(this.'$data')
    // 3. Compile the template
    new Compile('#app'.this)}}class Compile {
  // El - host element, vM-kvue instance
  constructor(el, vm) {
    this.$el = document.querySelector(el)
    this.$vm = vm

    // Parse the template
    if (this.$el) {
      / / compile
      this.compile(this.$el)
    }
  }

  compile(el) {
    // el is the host element
    // Iterate over it to determine the type of the currently traversed element
    el.childNodes.forEach(node= > {
      if (node.nodeType === 1) {
        // console.log(' compile element ', no.nodename);
        this.compileElement(node)
      } else if (this.isInter(node)) {
        // Text, {{XXX}}
        // console.log(' compile text ', Node.textContent, RegExp.$1);
        this.compileText(node)
      }

      / / recursion
      if (node.childNodes && node.childNodes.length > 0) {
        this.compile(node)
      }
    })
  }

  // Determine the interpolation
  isInter(node) {
    return node.nodeType === 3 && / \ {\ {(. *) \} \} /.test(node.textContent)
  }

  // Compile text
  compileText(node) {
    this.update(node, RegExp. $1,'text')}// Compile elements: parse instructions, @ events
  compileElement(node) {
    // Get attributes and iterate over them
    const nodeAttrs = node.attributes

    Array.from(nodeAttrs).forEach(attr= > {
      // command: j-xxx="yyy"
      const attrName = attr.name  // j-xxx
      const exp = attr.value // yyy
      if (this.isDirective(attrName)) {
        const dir = attrName.substring(2) // xxx
        // command the actual operation method
        this[dir] && this[dir](node, exp)
      }
    })
  }

  isDirective(attr) {
    return attr.indexOf('j-') = = =0
  }

  // Execute the update function corresponding to the text instruction
  text(node, exp) {
    this.update(node, exp, 'text')}// j-text corresponds to the operation function
  textUpdater(node, val) {
    node.textContent = val
  }

  html(node, exp) {
    this.update(node, exp, 'html')}htmlUpdater(node, val) {
    node.innerHTML = val
  }

  // Extract the update, initialization, and update functions created for each departure
  update(node, exp, dir) {
    const fn = this[dir + 'Updater']
    / / initialization
    fn && fn(node, this.$vm[exp])

    / / update
    new Watcher(this.$vm, exp, function (val) {
      fn && fn(node, val)
    })
  }

}

// Watcher: small secretary, as in the view dependency 1:1
// const watchers = []
class Watcher {
  constructor(vm, key, updaterFn) {
    this.vm = vm
    this.key = key
    this.updaterFn = updaterFn
    // Rely on the collection trigger
    Dep.target = this
    this.vm[this.key] // Trigger get above
    Dep.target = null
  }
  update() {
    this.updaterFn.call(this.vm, this.vm[this.key])
  }
}

// Butler: corresponds to one key, one to many
class Dep {
  constructor() {
    this.deps = []
  }
  addDep(watcher) {
    this.deps.push(watcher)
  }
  notify() {
    this.deps.forEach(watcher= > watcher.update())
  }
}
Copy the code

3. Code optimization

3.1 Arrays support reactive

// Get the original array prototype method
const orginalProto = Array.prototype
// Create a backup
const arrayProto = Object.create(orginalProto)
['push'.'pop'.'shift'].forEach( method= > {
  // Keep the original operation
  orginalProto[method].apply(this.arguments)
  // Add a section operation
  console.log("Method currently called :",method)
})



// Do different operations depending on the type of value passed in
class Observer {
  constructor(value) {
    this.value = value 
    this.walk(value)
  }

  walk(obj) {
    if(Array.isArray(obj)) {// Add array judgment
      // Modify the prototype
      obj.__proto__ = arrayProto
      for (let index = 0; index < obj.length; index++) {
        walk(obj[index]);// Continue the recursive processing}}else {
      Object.keys(obj).forEach(key= > {
        defineReactive(obj, key, obj[key])
      })
    }
  }
}
Copy the code

3.2 Added @ Event Processing

  // Compile elements: parse instructions, @ events
  compileElement(node) {
    // Get attributes and iterate over them
    const nodeAttrs = node.attributes

    Array.from(nodeAttrs).forEach(attr= > {
      // command: k-xxx="yyy"
      const attrName = attr.name  // k-xxx
      const exp = attr.value // yyy
      if (this.isDirective(attrName)) {
        const dir = attrName.substring(2) // xxx
        // command the actual operation method
        this[dir] && this[dir](node, exp)
      }else if(this.isEvent(attrName)) { // Handle events
        const dir = attrName.substring(1)
        this.eventHandler(node,exp,dir)
      }
      // Handle events})}// Add event judgment
  isEvent(attr) {
    return attr.indexOf(The '@') = = =0
  }
  eventHandler(node,exp,dir) {
    // The methods (key value) object in the VM
    const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp]
    fn.bind(this.$vm) // Resolve call binding issues
    node.addEventLister(dir,fn)
  }

Copy the code

3.3 v – model

model(node,exp) {
    this.update(node,exp,'model')
    // Add events
    node.addEventListener('input'.e= > {
        this.$vm[exp] = e.target.value
    })
}
// Direct node assignment
modelUpdater(node,value) {
    node.value = value
}
Copy the code