Reactive is different from Responsive in the CSS layout, where the data is bound to the view and the view updates automatically when the data is updated. Let’s take a look at how Vue implements responsiveness. Vue 2.0 and Vue 3.0 implement different principles, so let’s break them down.

Responsiveness of Vue 2.0

Object.defineProperty

The responsivity of Vue 2.0 mainly uses Object.defineProperty. Let’s start with this method. Object.defineproperty (obj, prop, descriptor) is used to defineProperty descriptors. It takes three parameters. The first parameter is the target Object, the second parameter is the property in the target Object, and the third parameter is the property descriptor you want to set

{value: undefined, // attribute value get: undefined, // attribute value set: undefined, // attribute value set: writable: Enumerable: false // Enumerable: false Keys () enumerates a different property, which can be deleted without any additional information, and runs without any additional information.Copy the code

For an ordinary object

var a = {b: 1}
Copy the code

We can use the Object. GetOwnPropertyDescriptor to obtain an attribute of the descriptor

The writable, Enumerable, and 64x descriptors of the a/B property are all true, and their default value is false. This is because the way we define a property is different, we already assigned it a value when we first defined it, so it’s already writable. Instead, declare A.C directly with Object.defineProperty and look at its property descriptor

Writable, Enumerable, and 64x are default false, which means that no modifications, enumeration, and different configurations are allowed. The writable, enumerable, and 64x are different, and no additional information is configurable. Even if you explicitly say a.c=3, it doesn’t work; it still has a value of 2, and writing this in strict mode will cause an error. Because the 64x is false, you cannot modify the descriptor via Object. DefineProperty without any additional information, any error is reported:

Set and get

That’s the big deal. A Vue is a response via set and GET. We’ll explain this by implementing a simple version of Vue ourselves. First let’s define a vUE:

function vue(){
    this.$data = {a: 1};
  this.el = document.getElementById('app');
  this.virtualDom = '';
  this.observer(this.$data);
  this.render();
}
Copy the code

We need to implement set and GET in the Observer method because we are listening for value properties if the property itself is an object, for example

{
  a: {
    b: {
      c: 1
    }
  }
}
Copy the code

We need to recursively set set and get to listen for values inside. Our simple version of GET returns the value directly, but we can actually optimize that, and we’ll talk about that later. The set method takes a parameter newValue, which we assign directly to value, and then call the Render method to update the interface

vue.prototype.observer = function(obj){ var value; var self = this; For (var key in obj){// Set and get value = obj[key]; if(typeof value === 'object'){ this.observer(value); } else { Object.defineProperty(this.$data, key, { get: function(){ return value; }, set: function(newValue){ value = newValue; self.render(); }}); }}}Copy the code

Here’s the Render method:

vue.prototype.render = function(){
    this.virtualDom = `I am ${this.$data.a}`;
  this.el.innerHTML = this.virtualDom;
}
Copy the code

This way the interface will update automatically every time you modify $data.a. Note that if you set get but do not write the return value, undefined will be returned by default. Every time you read this property, undefined will be returned. If you set set, the update value must be implemented all by itself. In fact, there are a lot of things that need to be optimized for get and set. We are now updating the entire DOM as soon as the set is triggered, but in reality we might have 100 components and only one of them uses the value of the set, which would be a huge waste of resources. We need to find out which components a variable is used by, and only update those components when the variable is updated. Here’s what Vue really does:

So our get and set look like this:

Object.defineProperty(this.$data, key, { get: function(){ dep.depend(); Return value; }, set: function(newValue){ value = newValue; // self.render(); dep.notify(); // Here is a virtualDom update to notify the component to be updated render}});Copy the code

Dep is a class that Vue is responsible for managing dependencies, covered in a separate article.

Array handling

Arrays cannot be handled with Object.defineProperty. What should I do? In Vue, it is useless to manipulate arrays by subscript. You must use push, shift, etc. Why?

var a = [1, 2, 3]; a[0] = 10; // This does not update the viewCopy the code

In fact, Vue overrides the array methods with decorator mode. Before we get to that, let’s talk about Object.create

Object.create

This method creates a new Object, uses the existing Object to provide the newly created Object’s __proto__, and takes two arguments object.create (proto[, propertiesObject]). The first argument is the prototype object of the newly created object. The second argument, if not specified as undefined, is the property descriptor and the corresponding property name of the non-enumerable (default) property to be added to the newly created object (that is, properties defined by itself, rather than enumerated properties in its stereotype chain). These properties correspond to the second argument to Object.defineProperties(). See an example of implementing class inheritance:

// Shape - superclass function Shape() {this.x = 0; this.y = 0; Function (x, y) {this.x += x; // function(x, y) {this.x += x; this.y += y; console.info('Shape moved.'); }; // Rectangle - subclass (subclass) function Rectangle() {shape.call (this); Rectangle. Prototype = object.create (Shape. Prototype); // Rectangle. // Rectangle. Prototype! = = Shape. The prototype / / but a Rectangle. The prototype. __prpto__ = = = Shape. The prototype Rectangle. The prototype. The constructor = a Rectangle; Rect = new Rectangle(); console.log('Is rect an instance of Rectangle? ', rect instanceof Rectangle); // true console.log('Is rect an instance of Shape? ', rect instanceof Shape); // true rect.move(1, 1); // Outputs, 'Shape moved.'Copy the code

Multiple inheritance examples:

function MyClass() { SuperClass.call(this); OtherSuperClass.call(this); Prototype = object.create (superclass.prototype); / / mixed other Object. The assign (MyClass. Prototype, OtherSuperClass. Prototype); / / to specify the constructor MyClass. Prototype. Constructor = MyClass; MyClass.prototype.myMethod = function() { // do a thing };Copy the code

Let’s go back to Vue and look at its array decorator mode:

var arraypro = Array.prototype; Var arrob = object.create (arrayPro); // Create a new object with the Array prototype, arrob.__proto__ === arrayPro, so as not to contaminate the original Array; var arr=['push', 'pop', 'shift']; Arr. ForEach (function(method){arrob[method] = function(){arraypro[method]. Apply (this, arguments); // Call the native method dep.notify(); // And also update}}); Var a = [1, 2, 3]; // For user-defined arrays, manually set __proto__ of the array to the prototype we modified. a.__proto__ = arrob;Copy the code

For the new arrob method above, we assign the value directly. There is a problem that users might accidentally change our Object, so we can use object.defineProperty to get around this problem. We create a public method def specifically for setting properties that cannot be modified

Function def (obj, key, value) {object.defineProperty (obj, key, value) {Object. true, configurable: true, value: value, }); Function (method){def(arrob, method, function(){arrayPro [method]. Apply (this, arguments); // Call the native method dep.notify(); }); });Copy the code

Vue 3.0 responsivity

3.0 is responsive in a similar way to 2.0, collecting dependencies on GET and updating views on set. But 3.0 uses ES6’s new API Proxy and Reflect, and using Proxy over Object.defineProperty has the following benefits:

DefineProperty needs to specify objects and attributes. For multi-layer nested objects, recursive listening is required. Proxy can directly listen on the whole Object without recursion. Object.defineproperty's get method has no parameters. If we need to return the original value, we need to cache the previous value externally. Proxy's get method passes in objects and attributes and can operate directly inside the function without external variables. Object.defineproperty's set takes only newValue, which needs to be manually assigned to an external variable. Proxy's set also takes objects and attributes, which can be manipulated directly inside the function. 4. New Proxy() returns a new object without polluting the original object. 5Copy the code

The above vue.prototype.observer can be changed to:

vue.prototype.observer = function(obj){ var self = this; this.$data = new Proxy(this.$data, { get: function(target, key){ return target[key]; }, set: function(target, key, newValue){ target[key] = newValue; self.render(); }}); }Copy the code

Proxy can also perform data verification

Var validator = {name: function(value){var reg = /^[u4E00-\u9FA5]+$/; if(typeof value === "string" && reg.test(value)){ return true; } return false; }, age: function(value){ if(typeof value==='number' && value >= 18){ return true; } return false; } } function person(age, name){ this.age = age; this.name = name; Return new Proxy(this, {get: function(target, key){return target[key]; }, set: Function (target, key, newValue){function(target, key, newValue){ If (validator[key](newValue)){return reflect. set(target, key, newValue); if(validator[key](newValue)){return reflect. set(target, key, newValue); }else{ throw new Error(`${key} is wrong.`) } } }); }Copy the code

let p = new Proxy(target, handler); The second argument to handler can be fired not only on get and set, but also on the following methods:

getPrototypeOf()
setPrototypeOf()
isExtensible()
preventExtensions()
getOwnPropertyDescriptor()
defineProperty()
has()
get()
set()
deleteProperty()
ownKeys()
apply()
construct()
Copy the code

Discussion on virtual DOM and Diff algorithm

We have a template like this:

<template>
  <div id="123">
    <p>
      <span>111</span>
    </p>
    <p>
      123
    </p>
  </div>
  <div>
    <p>456</p>
  </div>
</template>
Copy the code

This template is converted to pseudocode for the virtual DOM, using the first div as an example:

{ dom: 'div', props: { id: 123 }, children: [ { dom: 'p', children: [ dom: 'span', text: "111" ] }, { dom: 'p', text: "123"}}]Copy the code

Each node can have multiple children. Each child is a single node with the same structure and can also have its own children.

When performing node comparison, Vue will only perform same-level comparison, for example, before a node is:

<div>
    <p>123</p>
</div>
Copy the code

And then it becomes

<div>
    <span>456</span>
</div>
Copy the code

Only div of the first layer will be compared, and p and SPAN of the second layer will not be compared, as shown below:

The DIff algorithm starting from the set method of data changes is shown in the figure below:

If isSameVnode returns false, the entire node is updated. If the nodes themselves are the same, their children are compared.

patchVnode(oldVnode, vnode){ const el = vnode.el = oldVnode; let i, oldCh = oldVnode.children, ch = vnode.children; if(oldVnode === vnode) return; if(oldVnode.text ! == null && vnode.text ! == null && oldVnode.text ! == vnode.text){// Just change the text, update the text setTextContext(el, vnode.text); }else{ updateEle(); if(oldCh&&ch&&oldCh! ==ch){// All have child elements, but change updateChildren(); }else if(ch){// Create a new element createEl(vnode); }else if(oldCh){removeChildren(el); }}}Copy the code

At the end of this article, thank you for your precious time to read this article. If this article gives you a little help or inspiration, please do not spare your thumbs up and GitHub stars. Your support is the motivation of the author’s continuous creation.

Welcome to follow my public numberThe big front end of the attackThe first time to obtain high quality original ~

“Front-end Advanced Knowledge” series:Juejin. Im/post / 5 e3ffc…

“Front-end advanced knowledge” series article source code GitHub address:Github.com/dennis-jian…