Realization of the array of hijacking

As mentioned in the previous article, object.defineProperty can’t be used to hijack an array, so what does Vue do to solve this problem? Vue is a prototype object that creates an Array. The prototype of this object points to array. prototype. The specific implementation code is as follows:

const arrayProto = Object.create(Array.prototype)
let methodName = ['push'.'pop'.'shift'.'unshift'.'reverse'.'sort'.'splice']

methodName.forEach(method= > {
  arrayProto[method] = function(. args) {
  // The element of an array push can be an object, and it is reactive to handle the element of an array as an object
    if(method === 'push') {
      this.__ob__.observeArray(args)
    }
    const result = Array.prototype[method].apply(this, args)
    this.__ob__.dep.notify()
    return result
  }
})
// Then use the Observer method above to determine whether the current listening object is an array
class Observer {
  constructor(data) {
    this.dep = new Dep()
    if(Array.isArray(data)) {
      data.__proto__ = arrayProto
      this.observeArray(data)
    } else {
      this.walk(data)
    }
    Object.defineProperty(data, '__ob__', {
      value: this.enumerable: false.writable: true.configurable: true})}// Treat the elements of the array as objects, and make them responsive
  observeArray(arr) {
    for(let i = 0; i < arr.length; i++) { observe(arr[i]) } } ... }Copy the code

To realize the computed

Those of you familiar with VUE know that computed attributes depend on data in data, and that computed attributes are automatically updated when data in data changes, and that attributes in computed attributes cannot be set manually. It should have the advantages of inertia and caching. In addition to the use of computed data, You mentioned in the official website that expressions in vUE templates are designed for simple calculations, and complex logical operations involved in templates should be included in calculation attributes. Now we start implementing a computed property:

class MVue{
  constructor(){
    this.ininComputed()
    // Watcher can listen on attributes in computed
    this.initWatcher()
 }
 ...
  initComputed() {
    let computed = this.$options.computed
    if(computed) {
      Reflect.ownKeys(computed).forEach(watcher= > {
        const compute = new Watcher(this, computed[watcher], () = > {})

        Object.defineProperty(this, watcher,{
          enumerable: true.configurable: true.get: function computedGetter() {
            compute.get()
            return compute.value
          },
          set: function computedSetter() {
            console.warn('Compute property not assignable')}})})}}}Copy the code

This is just a simple implementation of computed, but we all know that the computed property has two characteristics:

    1. The calculated property isinertWhen other attributes on which the calculated attribute depends change, the calculated attribute is not immediately reexecuted until it is retrieved.
    1. The calculated property isThe cacheIf there is no change in other attributes that the calculated attribute depends on, the calculated attribute will not be recalculated even if it is re-evaluated.
class Watcher {
  constructor(vm, exp, cb, options = {}) {
    this.lazy = this.dirty = !! options.lazythis.vm = vm
    this.exp = exp
    this.cb = cb
    this.id = ++watchId
    if(!this.lazy) {
      this.get()
    }
  }

  get() {
    Dep.target = this
    if(typeof this.exp === 'function') {this.value = this.exp.apply(this.vm)
    } else {
      this.value = this.vm[this.exp]
    }
    Dep.target = null
  }
  // The run method does not implement laziness when lazy is true
  update() {
    // If it is lazy, do not execute it
    if(this.lazy) {
      this.dirty = true
    } else {
      this.run()
    }
  }

  run() {
    if(watcherQueue.includes(this.id)) {
      return
    }
    watcherQueue.push(this.id)
    Promise.resolve().then(() = >{
      this.cb.call(this.vm)
      watcherQueue.pop()
    })
  }
}
// Change initComputed as follows
 // Initialize the calculated properties
  initComputed() {
    let computed = this.$options.computed
    if(computed) {
      Reflect.ownKeys(computed).forEach(watcher= > {
        const compute = new Watcher(this, computed[watcher], () = > {}, { lazy: true })
        Object.defineProperty(this, watcher,{
          enumerable: true.configurable: true.get: function computedGetter() {
          // Get cache if compute is dirty!
            if(compute.dirty) {
              compute.get()
              compute.dirty = false
            }
          // Return the result of the last read
            return compute.value
          },
          set: function computedSetter() {
            console.warn('Compute property not assignable')}})})}}Copy the code

Template compilation

Simple implementation of vUE template compilation, we can use

new Watcher(this.() = > {
    document.querySelector('#app').innerHTML = `<p>The ${this.name}</p>`
}, () = >{})
Copy the code

1. However, this implementation can use template syntax and requires some processing of the template, which is ultimately converted into a function that performs DOM updates 2. Replacing all the DOM directly is expensive, so it’s best to update the DOM as needed.

In order to minimize unnecessary DOM manipulation and achieve cross-platform features, Virtual dom is introduced in VUE

What is the vdom? It’s basically a JS object that describes what the DOM looks like.

To get the VDOM of the current instance, each instance needs to have a render function to generate the VDOM, called the render function

When a Vue instance is passed dom or template, the first step is to convert the template string into a rendering function, which is compiled.

Vue compilation principle

  • 1. Convert template strings to the Element AST parser.)
  • 2. Static node marks for AST to optimize VDOM rendering
  • 3. Use element ASTs to generate the render function code string

Note: AST is a code into another code, is a description of source code. Vue will treat the template structure as a string. Here, I will simulate the template compilation in VUE to implement the element node Parser, as follows: (An AST structure is generated after parser)

/ / the parser
function parser(html){
  let stack = []
  let root
  let currentParent
  while (html) {
    let index = html.indexOf('<')
    // There are text nodes in front of it
    if (index > 0) {
      let text = html.slice(0, index)
      const element = {
        parent: currentParent,
        type: 3,
        text
      }
      currentParent.children.push(element)
      // Intercepts HTML as the rest of the HTML except for the text node
      html = html.slice(index)
    } else if( html[index + 1]! = ='/' ) {
      // There is no text node in front and a start tag
      let gtIndex = html.indexOf('>')
      const element = {
        type: 1.tag: html.slice(index + 1, gtIndex),
        parent: currentParent,
        children: []}if(! root) { root = element }else {
        currentParent.children.push(element)
      }
      stack.push(element)
      currentParent = element
      html = html.slice(gtIndex + 1)}else {
      // End tag
      let gtIndex = html.indexOf('>')
      stack.pop()
      currentParent = stack[stack.length - 1]
      html = html.slice(gtIndex + 1)}}return root
}

// Parse a text node
function parseText(text) {
  let originText = text
  let type = 3
  let tokens = []
  while(text) {
    let start = text.indexOf('{{')
    let end = text.indexOf('}} ')
    if(start ! = = -1&& end ! = = -1) {
      type = 2
      if(start > 0) {
        tokens.push(JSON.stringify(text.slice(0, text)))
      }
      let exp = text.slice(start + 2, end)
      tokens.push(`_s(${exp}) `)
      text = text.slice(end + 2)}else {
      tokens.push(JSON.stringify(text))
      text = ' '}}let element = {
    text: originText,
    type
  }
  if(type === 2) {
    element.expression = tokens.join('+')}return element
}
Copy the code

After the AST is generated by Parser, we need to convert the AST into a rendering function.

  • 1. Recursive AST, the following structure is generated when the element node is encountered_c(Tag name, attribute object, descendant array)
  • 2. If a text node is encountered, the following structure is generated for plain text_v(text string)
  • 3. If a text node with a variable is encountered, it is generated_v(_s(variable name))
  • 4. In order to make variables can be retrieved normally, generate the last string package layerwith(this)
  • 5. Finally, generate a function from the string as a function and mount it tovm.$optionson

$option.render. Call (vm); $option.render. Call (vm);

// type: 1 element node 2 text node with variables 3 plain text node
// The core method converts the AST to the render function
function generate(ast) {
  let code = genElement(ast)
  return {
    render: `with(this)${code}`}}// Convert the element node
function genElement(el) {
  let children = genChildren(el)
  return `_c(The ${JSON.stringify(el.tag)}, {}, ${children}) `
}
// Iterate over descendant nodes
function genChildren(el){
  if(el.children.length) {
    return '['+ el.children.map(child= > genNode(child)) +'] '}}// Convert the text node
function genText(node) {
  if(node.type === 2) {
    return `_v(${node.expression}) `
  } else if( node.type === 3 ) {
    return `_v(The ${JSON.stringify(node.text)}) `}}// Convert nodes
function genNode(node) {
  // Element node
  if(node.type === 1) {
    return genElement(node)
  } else {
    return genText(node)
  }
}
Copy the code

The next installment will continue to implement a VDOM, along with some analysis of common vUE principles