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

This article starts from the basic design idea of VUE series, and realizes the basic API of handwriting. Let everyone experience the mystery of vUE’s data-driven from practice.

Design concept

As you all know, VUE is a typical MVVM framework. What is MVVM and how is it represented in VUE?

MVVM

M

M stands for Model Model. Is the data displayed in the page, and in VUE refers to the data model in data.

V

V stands for View. Is the displayed page, pointing to the template engine in VUE.

VM

VM stands for ViewModel. There is no need for us to parse the data and present it to the view, and when the data changes, the view on the page automatically changes accordingly.

It is important to mention the data-driven nature of VUE in terms of how vUE separates views from logical operations. So how is this data presented in the view? Vue listens for changes in data and updates it in the view through data responsiveness. The template engine provides template syntax (htML-like) for describing views. Provides some VUE specific instructions, interpolation); Render: Convert the template syntax to HTML (AST=> vDOM => DOM).

Data response

Simple response

See this article for a simple example of the response data in vUE: vue2: object.defineProperty () vue3: Proxy.

<div id="app"></div>
<script>
function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    get() {
      return val
    },
    set(newVal) {
      if(val ! = newVal) val = newVal// Update the view when data changes
      update()
    }
  })
}
function update() {
  content.innerHTML = `<h1>${obj.name}</h1>`
}
let obj = { name: 'clying' }
let content = document.getElementById('app')
update()
// Reactive processing
defineReactive(obj, 'name'.'clying')
setTimeout(() = > {
obj.name = 'deng'
console.log(obj.name);
}, 1000)
</script>
Copy the code

Running the code, we can see that the page starts with Clying and changes to Deng after 1 second. With this simple example, we can implement a simple OBJ object to render the page responsively.

Recursive response

In the example above, only the object deep=1 was monitored. So how does an OBj object with depth listen for changes?

At this point, you need to traverse the OBJ object and listen for data on each attribute in the object. Returns an array of strings in obj via object. keys, and intercepts it with the setter and getter.

// Let's start with an object with depth
let obj = {
  name: 'clying'.arr: [
    1,
    {
      namearr: '2'],},children: {
    name1: 'deng'.children: {
      name2: 'clying deng',,}}}function defineReactive(obj, key, val) {
  observe(val) // The child property may still be an object and is being intercepted
  Object.defineProperty(obj, key, {
    get() {
      console.log('get', key)
      return val
    },
    set(newVal) {
      if(newVal ! == val) val = newValconsole.log('Set new value', key, obj[key])
    },
  })
}
function observe(obj) {
  if (typeofobj ! = ='object' || obj === null) return
  Object.keys(obj).forEach((key) = > defineReactive(obj, key, obj[key]))
}
observe(obj)
obj.children.name1 = 'l'
Copy the code

When settingobj.children.name1, first get the children attribute, find that children is still an object, continue traversal. When the name1 attribute in children is obtained, it is found that it is not an image, so name1 is intercepted and a new value is set.

Add properties dynamically

When I want to dynamically add an age attribute to obj, as in the above example, it doesn’t really work. DefineProperty’s inability to detect new attributes also relates to a disadvantage of VUE2. This requires additional use of the SET API method in VUE2.

function set(obj, key, val) {
  defineReactive(obj, key, val)
}
set(obj, 'age'.22)
obj.age
Copy the code

As you can see, the set method also uses defineProperty to add new attributes, but needs to be called manually by the user. The new property age in OBj can be intercepted by calling set.

Note the use of set: the set method must be responsive to the target parameter it receives. As you can see in the source set method, it initially checks whether the target value passed in is raw, undefined, or null, and if so warns directly. If you want to delete the property, again, you need to manually call the delete method. Note: The observer folder index.js is at line 201

Array response

DefineProperty can intercept an array like arr[0] = 1, assigned by index. However, it does not support array push, pop and other array prototype methods. We need to intercept 7 methods of the array, override them, just do it!

Because we’re simply mimicking, we didn’t write the Observe class. Here I break the Observe class into the Observe method (to determine whether it is an array or an object, and listen separately) and the observeArray loop through the listen array property.

function observeArray(arr) {
  arr.forEach((_) = > observe(_))
}
function observe(obj) {
  if (typeofobj ! = ='object' || obj == null) return
  if (Array.isArray(obj)) {
    obj.__proto__ = arrayMethods // Inherit the stereotype method attributes
    observeArray(obj)
  } else {
    Object.keys(obj).forEach((key) = > defineReactive(obj, key, obj[key]))
  }
}
Copy the code

__proto__ = arrayMethods (arrayMethods) ¶ The core is to make the array prototype chain we are currently traversing point to the array method we are overwriting.

const methodsToPatch = [
  'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse',]let oldArrayMethods = Array.prototype
let arrayMethods = Object.create(oldArrayMethods) // The stereotype of arrayMethods points to the stereotype of Array Array
methodsToPatch.forEach((method) = > {
  arrayMethods[method] = function (. args) {
    const result = oldArrayMethods[method].apply(this, args)
    let inserted // The element inserted by the current user
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      Arr. splice(0,1,{name: 1})
      case 'splice':
        inserted = args.slice(2)
      default:
        break
    }
    // let ob = this.__ob__;
    // ob.observeArray(inserted); // If you insert an object or an array, you need to recursively listen again
    // update Notification update
    return result
  }
})
Copy the code

When we pass obj.arr.push(1); Obj. Arr [1]. Namearr = 2

When you push the array method and modify the array value, the array can go to defineProperty and be intercepted. At this point, you still need to listen for the value of an array insertion when it could be an object or an array. You should first save this instance in the Observe class (with the observeArray method inside). Then, using its observeArray method in arrayMethods, the deep hijacking continues.

At this point, on the data response type can come to an end. If there is insufficient, welcome to correct.