Response principle

Vue’s official website defines vuejs as: progressive javascript framework: If you have an existing server application, you can embed vUE as part of the application for a richer business experience. If you want more interaction logic on the front end, the vUE core library and ecosystem can also meet your needs

1. What is reactive

Generally speaking, responsiveness is the bidirectional binding between the view layer and the data layer. There are two core elements of responsiveness: view changes, updating data; Data changes, changes the view. This means that we only need to manage data at development time and do not need to manipulate DOM elements frequently.

View changes update data, which is easy to implement by listening for events, such as input tags listening for ‘input’ events. But how do you update the view when the data changes? This requires knowing when the data has changed, and when we know the data has changed, we can update the view.

2. Implementation:

Vue2.0 is an observer pattern using setters and getters in the object.defineProperty () method.

Vue3.0 intercepts attributes based on Proxy and implements bidirectional binding.

3. Object.defineproperty () and two-way binding (vue2.x two-way binding implementation)

Object.defineproperty () directly defines a new property on an Object, or modifies an existing property of an Object, and returns the Object. This function takes three parameters, one parameter is obj, which is the object to define the property, one parameter is prop, which is the name of the property to define or change, and the other parameter descriptor, which defines the description of the property. Object.defineProperty(obj, prop, descriptor)

3.1 Objects and Properties

There are three types of properties in javascript:

Common attributes: that is, the general data attributes, this attribute is the user to add and modify, etc., set it to what, return what, do not do anything extra. We usually use dots or square brackets to assign and access common attributes

let obj = {};
obj.x = 1;
obj['y'] =12;
console.log(obj.x);  //  1
console.log(obj.y);  //  12
let proprrty = Object.keys(obj);
console.log(proprrty) // ["x", "y"]
delete obj.x;
console.log(obj.x);  // undefined
Copy the code

Internal properties: such as the length of the array arr, the prototype property of the function, and the innerHTML property of the DOM node. When users assign a value, the value is not always as expected, and sometimes they do something extra, which makes it difficult to change their behavior. Let’s say we have an array of length 10, and when we set it to 11, we add a undefined element, and when we set it to 9, we delete two elements. If the function’s prototype is changed, it changes its parent class, creating a new instance of a different type. The innerHTML of the DOM, when we assign it as a string, when we take it out, that string may be different from the original one, with a different child node on the original element.

Accessor property: a property defined via Object.defineProperty() that the user assigns or values through predefined functions to achieve the special effect of an internal property.

For ordinary properties, Object properties can be modified, deleted or enumerated, but defining properties with Object.defineProperty() and setting descriptors allows more precise control over Object properties.

Attribute descriptor of 3.2 descriptor

There are two types of attribute descriptors: data descriptors and access descriptors. A data descriptor is a property with a value that can be writable or unwritable. (value, writable, configurable, enumerable). Access descriptors are properties described by getter and setter functions. (get, set, configurable, enumerable). The default of any Boolean key, Enumerable, and Writable is false. The default value for attribute values and the key value, get, and set fields of functions is undefined.

Data descriptorconst obj = {};

// x property value cannot be modified Writable Enumerable cannot be modified
Object.defineProperty(obj, 'x', {
  value: 1.// Can change the default false
  writable: false.// Can be deleted, and can other features besides value and writable be modified
  configurable: false.// Can you do this for... Are enumerated in the in loop and object.keys ()
  enumerable: false}); obj.x =77; 
console.log(obj.x);    / / 1
delete obj.x 
let property = Object.keys(obj);
console.log(property) / / []
Object.defineProperty(obj, 'x', {configurable: true}) // throw error

// y attribute value cannot be modifiable Writable Enumerable
Object.defineProperty(obj, 'y', {
  value: 1.writable: false.configurable: true.enumerable: false}); obj.y =12;
console.log(obj.y);    / / 1
// Different True allows you to change writable Enumerable
Object.defineProperty(obj, 'y', {writable: true});
obj.y = 12;
console.log(obj.y);    / / 12
property = Object.keys(obj);
console.log(property) / / []
Object.defineProperty(obj, 'y', {enumerable: true});
property = Object.keys(obj);
console.log(property) //["y"]
Copy the code
Access descriptorconst obj = {};
let val;
Object.defineProperty(obj, 'x', {
  get() { 
    console.log('trigger get')
    return val; 
  },
  set(newValue) {
    console.log('the trigger set')
    val = newValue; 
  },
})
obj.x = 12; / / triggers the set
console.log(obj.x)  // Trigger get 12

Copy the code

3.2 Simple bidirectional binding via Object.defineProperty()

Implementation code:

<div>
    <input type="text" id="value"/>
    <span id="bindValue"></span>
</div>
Copy the code
// View interactive control
let inputEle = document.getElementById('value');
let spanEle = document.getElementById('bindValue');
const MessageObj = {};
Object.defineProperty(MessageObj, 'msg', {
  set: function (newVal) {
    inputEle.value = newVal;
    spanEle.innerText = newVal
  }
})
// Listen for input view changes to update data
inputEle.addEventListener('keyup'.function (event) {
  MessageObj.msg = event.target.value
})
Copy the code

Effect:

3.3 Observer Mode

Vue. js uses data hijacking combined with observer mode (publisher – subscriber mode). It uses Object.defineProperty() to hijack the setter and getter of each attribute to publish messages to subscribers when data changes and trigger corresponding listener callback. Now that we know a little bit about Object.defineProperty(), let’s move on to what observer mode is. concept

* * * * the Observer pattern (the Observer) is usually referred to as post messages – subscriber mode or mechanism, it defines a one-to-many dependency between objects, as long as when an object’s state is changed, all depend on its object of warning and is automatically updated and solved the main function of the coupling between object and the Observer, That is, a change in the state of one object notifies other objects.

The definition above is quite official. In a word, subscriber mode is divided into registration and publication, which can be more commonly understood by using scenes in life.

A popular understanding of the story

Suppose we go to the bank to do business, because there are many people doing business, we will be asked to take a number on the number machine, this is the registration process. Bank clerk to deal with the business personnel is one-to-many dependency, the bank clerk to deal with plate tasks one by one, didn’t finish a task, is required to notify all dependent objects (do business) the change of the current state, the bank office hall led sign will always show the current number is do business as well as the next one will be dealt with the number of business, Bank employees who handle business are publishers. Led displays maintain a list of observers, and we who pay attention to led displays are subscribers. When it is our turn to handle business with our plates, we will be informed to handle business at the designated counter, which is the release link.

Simple observer modelfunction observer () {
  this.dep = [];

  this.register = (fn) = > {
    this.dep.push(fn);
  }

  this.notify = (a)= > {
    this.dep.forEach(item= > item())
  }
}

let bankObserver = new observer();
bankObserver.register( (a)= > {console.log("Cash out")}); bankObserver.register((a)= > {console.log(",")}); bankObserver.notify();Copy the code

From the above example we can extract the three elements of the observer pattern: publisher, subscriber, and cache list. In a nutshell, the observer pattern is that an object (publisher) maintains a list of dependent objects (cache list) and automatically notifies all subscribers when its state changes. When an object does not need notification, it can be removed from the object list.

Observation mode in javascript

JavaScript is an event-driven language. The implementation of the observer pattern mainly depends on the event model. For example, onClick, attachEvent and addEventListener are all specific applications of the observer pattern.

// javascript let spanEle = document.getelementByid ('span-click'); Spanele.addeventlistener ('click',function(){alert(' I'm an observer, when you click, I know '); });Copy the code

3.4 Bidirectional binding implementation combining observer mode with Object.defineProperty()

Bidirectional binding process:
  1. When the component initializes, it iterates through each property of data and registers the setter and getter for each property using Object.defineProperty(), also known as reactice.
  2. Each component instance corresponds to a Watcher instance, which records “touched” data properties as dependencies during component rendering. Watcher is then notified when the setter for the dependency fires, causing its associated component to be re-rendered.
What is dependency?

Dependencies are just curly brace expressions and instruction expressions

<input type="text" id="value" v-model="value"/> // {{bindValue}} is also dependent <span>{{bindValue}}</span>Copy the code

Obviously, each dependency corresponds to the data in data, so dependencies can be understood simply as the relationship between the view layer and the data layer: the view layer shows the data-dependency relationship that depends on the data layer. The purpose of dependency collection is to prepare for future changes in the data, so that when the dependent data changes, the dependency location display can be updated accurately and in batches.

Data flow chart

The code implementation is as follows:


If you want to listen for all attributes, you can recursively traverse all attribute values and apply object.defineProperty ()
  function observe(data){
    if(! data ||typeofdata ! = ='object') {
      return;
    }
    Object.keys(data).forEach((key) = >{
      defineReactive(data, key, data[key])
    })
  }
  function defineReactive (obj, key, value){
      let dep = new Dep();
      observe(value);
      Object.defineProperty(obj, key, {
        enumerable: true.configurable: true.set: (newValue) = > {
            console.log('Value set')
          if( newValue ! == value ){ value = newValue;console.log(dep,'dep')
              // Update the viewdep.notify(); }},get: (a)= > {
            console.log('Value obtained',Dep)
          if(Dep.target) {
              dep.addSub(Dep.target);
          }
          returnvalue; }})}// Rely on collecting the subscriber list
  // Collect subscribers, and then execute the corresponding subscriber update function when attributes change
  function Dep () {
      this.subs = [];
  }
  Dep.prototype = {
      addSub: function(sub) {
          this.subs.push(sub);
      },
      notify: function() {
          console.log(this.subs,'this.subs')
          this.subs.forEach(function(sub) { sub.update(); }); }};/ / subscriber
  function Watcher(data, key,cb) {
      this.cb = cb;
      this.data = data;
      this.key = key;
      // Add yourself to the DEp by triggering the getter for the property
      this.value = this.get(); 
  }
  Watcher.prototype = {
      update: function() {
          this.run(); // Attribute value changes are notified
      },
      run: function() {
          var value = this.get(); // Get the latest value
          console.log(value,'Fetch to latest value')
          var oldVal = this.value;
          if(value ! == oldVal) {this.value = value;
              this.cb(); // Execute the callback bound in Compile to update the view}},get: function() {
          Dep.target = this;  // Point the current subscriber to yourself
          var value = this.data[this.key];   // Trigger the getter to add itself to the property subscriber
          Dep.target = null;  // Set the value
          returnvalue; }};function SelfVue (data, key, cb) {
      this.data = data;
      observe(data);
      cb(); // Initializes the value of template data
      new Watcher(this.data, key,cb);
      return this;
  }
Copy the code
<script src="./vue/vue.js"></script>
<div id="box">
  <span id="app"></span> 
  <span id="add" style="margin-left: 10px; display: inline-block; width: 10px; height: 10px; cursor: pointer;">+</span>
</div>
<script>
    let ele = document.getElementById('app');
    let btn = document.getElementById('add');
    let o = {
            number: 1,}var selfVue = new SelfVue(o , 'number',
      ()=>{ ele.innerHTML = o.number}
    );
    btn.addEventListener('click', () => {
      o.number += 1;
    },false)
</script>
Copy the code

The effect is as follows:

4. Proxy and bidirectional binding (Vue3.0 bidirectional binding implementation)

DefineProperty () and observer mode are the way to implement two-way binding in Vue2.0, while VUe3.0 uses Proxy and reflect, the Proxy built-in tool in ES6’s new syntax. Let’s see how to implement the same functionality with Proxy.

4.1 know the Proxy

The concept of the proxy

Proxy objects are used to define custom behavior for basic operations (such as property lookups, assignments, enumerations, function calls, and so on).

In ES6, proxy can be translated as “proxy”. It is mainly used to change the default behavior of some operations. The proxy builds an interception layer on the outer layer of the target object, and certain external operations on the target object must pass this interception layer. This method takes two arguments: target-the target object (which can be any type of object, including a native array, a function, or even another Proxy) that you want to wrap (process) with a Proxy. Handler – An object, usually with functions as attributes, that define the behavior of agent P as it performs various operations.

Basic usage

Data interception: supports 13 types of interception, which is richer than Object.defineProperty. Example code is as follows: (1) Object data interception

let target = {};
let handler = {
  // Intercepts reading of object properties, such as proxy.a and proxy['a']
  get (target, key) {
    console.info(`Get on property "${key}"`);
    return target[key];
  },
  A = 1 and proxy['a'] = 1
  set (target, key, value) {
    console.info(`Set on property "${key}" new value "${value}"`);
    target[key] = value;
  },
  // Intercepting propKey in proxy returns a Boolean value
  has (target, key) {
    console.log(`is "${key}" in object ? `);
    // Hide some attributes
    if( key === 'a') {return false;
    }else{
      return key in target;
    }
    // return key in target;
  },
  The delete operator returns a Boolean value after intercepting the trap before deleting it
  deleteProperty(target, key){
    console.log(`delete key: "${key}"`);
    if(key === 'a') {delete target[key];
    }else{
        returntarget[key]; }},/ / intercept the attributes of the Object itself read operations, the intercept the following operations: Object. GetOwnPropertyNames method and Object getOwnPropertySymbols Object. The keys ()
  ownKeys (target) {
    console.log(`key in target`)
    // (1) Return normal
    // return Reflect.ownKeys(target);
    // (2) error 'ownKeys' on proxy: trap result did not include 'd'
    // Enumerable must be returned in an array
    // return ['b'] 
    / / (3)
    return ['b'.'d']},// Intercepts the Object.setPrototypeof method
  setPrototypeOf (target, newProto) {
    console.log(`new prototype set object`);
    return true
  }
  // This method is called when the prototype of the proxy object is read. The return value must be an object or NULL
  // Object.getPrototypeOf .instanceof
  // .__proto__ object.prototype.isPrototypeOf()
  // object.prototype.__proto__
  getPrototypeOf (target) {
    console.log(`prototype in object`);
    // return Object.getPrototypeOf(target);
    return null;
  },

  // Intercept Object.preventExtensions() and must be called internally
  preventExtensions(target) {
    console.log("called_pre");
    Object.preventExtensions(target);
    return true;
  },
  By default, objects are extensible: you can add new properties to them.
  // And their __proto__ attribute can be changed.
  / / Object. PreventExtensions Object. The seal or Object. The freeze method can tag a Object for an extension (non - extensible)
  // Its return value must be consistent with the isExtensible property of the target object
  // Object.isExtensible(proxy) === Object.isExtensible(target)
  isExtensible: function(target) {
    console.log("called");
    return Object.isExtensible(target);;
  },

  / / interception Object. GetOwnPropertyDescriptor (), returns a property description Object, or undefined.
  getOwnPropertyDescriptor(target, key){
    if (key === 'b') {
      // return; // error 'b' which exists in the non-extensible proxy target
      return Object.getOwnPropertyDescriptor(target, key);
    }
    return Object.getOwnPropertyDescriptor(target, key);  
  },


  // Intercepting Object.defineProperty() returns a Boolean value indicating success or failure
  defineProperty(target, key, descriptor){
    console.log(`Object.defineProperty property ${key}`);
    Object.defineProperty(target, key, descriptor)
    return true; }}let proxy = new Proxy(target, handler);

proxy.a = 1;         // Set on property "a" new value "1"
proxy.b = 2;         // Set on property "b" new value "2"

console.log(proxy.a);// Get on property "a" // 1

console.log("a" in proxy );// is "a" in object ? // false
console.log("b" in proxy );// is "b" in object ? // true

delete proxy.a; // delete key: "a"
console.log(proxy.a);// Get on property "a" // undefined
delete proxy.b; // delete key: "b"
console.log(proxy.b);// Get on property "b" // 2

proxy.c = 3;
Object.defineProperty(proxy,'d', {value:'4'.enumerable: false,})// (1) filter three types of attributes
// A property named Symbol that does not exist on the target object cannot be enumerable
// console.log(Object.keys(proxy)); // ['b', 'c']
// (3) return only b
console.log(Object.keys(proxy)); // ['b']


let newProxy = {};
Object.setPrototypeOf(proxy,newProxy);// new prototype set object
console.log(Object.getPrototypeOf(proxy));// prototype get object // {}

let flag = Object.isExtensible(proxy);// called
console.log(flag); // true
flag = Object.preventExtensions(proxy)// called_pre
console.log(flag); // Proxy {b: 2, c: 3, d: "4"}
flag = Object.isExtensible(proxy);// called
console.log(flag);//false

console.log("-- -- -- -- -- -- -- -- -- -- -- --");

console.log(proxy) // Proxy {b: 2, c: 3, d: "4"}
flag = Object.getOwnPropertyDescriptor(proxy, 'a');
console.log(flag) // undefined
flag = Object.getOwnPropertyDescriptor(proxy, 'c');
console.log(flag) // {value: 3, writable: true, enumerable: true, configurable: true}
flag = Object.getOwnPropertyDescriptor(proxy, 'b'); // Unextensible must be returned
console.log(flag) // {value: 2, writable: true, enumerable: true, configurable: true}

console.log("+ + + + + + + + + + + + + + +");
// Object.defineProperty(proxy, 'name', {
// value: 'proxy',
/ /}); // Error the non-extensible proxy target object results from the previous code execution
Object.defineProperty(proxy, 'c', {
    value: 'proxy'});console.log(proxy) // Proxy {b: 2, c: "proxy", d: "4"}

Copy the code

(2) Intercepting apply Construct

let handler1 = {
  // Intercepting the function call, call, and apply parameters respectively:
  //1. Target object 2. Context object of target object (this) 3. An array of parameters for the target object
  apply (target, thisArg, argumentsList) {
    console.log(`Calculate sum: ${argumentsList}`);
    return 'Proxy ok'
  },
  // The intercepting new command must return an object
  construct (target, args, newTarget) {
    console.log(`args is ${args}`)
    return new target('Modified Xiao Ming'); }}function sum (a,b){
  return a+b;
}
let proxy1 = new Proxy(sum, handler1)
console.log(sum(1.2)); / / 3
console.log(proxy1(1.2));Calculate sum: 1,2 // Proxy ok

function person (name) {
  this.name = name;
}
let proxy2 = new Proxy(person, handler1)
let person1 = new person();
console.log(person1);  // person {name: "name "}
let person2 = new proxy2();
console.log(person2); // person {name: "修改的小明"}
Copy the code

4.2 Proxy and bidirectional binding

Example effects:

Sample code:

<div>
    <input type="text" id="value"/>
    <span id="bindValue"></span>
</div>
Copy the code
let inputEle = document.getElementById('value');
let spanEle = document.getElementById('bindValue');
const MessageObj = {};
let basehandler = {
    set(target, key, newVal){
        target[key] = newVal
        spanEle.innerText = newVal
    }
}
let proxy = new Proxy(MessageObj, basehandler)
// Listen for input
inputEle.addEventListener('keyup'.function (event) {
    proxy.msg = event.target.value
})
Copy the code

4.3 know Reflect

Reflect objects, like Proxy objects, are a new API provided by ES6 for manipulating objects. Proxy intercepts to modify some methods, and Reflect migrates some methods to that object to make some methods more reasonable. At this stage, some methods are deployed on both Object and Reflect objects, and future new methods will only be deployed on Reflect objects. That is, from the Reflect object you can get the methods inside the language. Example code is as follows:

/ / ES5 writing
try{
  Object.defineProperty(target,property,attributes);
   //success
} catch(e){
  //failure
}

/ / ES6 writing
if(Reflect.defineProperty(target,property,attributes)){
  //success
} else{
  //failure
}

/ / ES5 writing
Function.prototype.apply.call(Math.floor,undefined[1.75]) / / 1

/ / ES6 writing
Reflect.apply(Math.floor,undefined[1.75]) / / 1
Copy the code

Reflect Object has a total of 13 static methods, most of which have the same function as the method of Object with the same name, and it is one-to-one corresponding to the method of Proxy Object. It can be said that as long as it is the method of Proxy Object, you can find the corresponding method on Reflect Object. This allows the Proxy object to easily call the corresponding Reflect method, completing the default behavior as a basis for modifying the behavior. That is, no matter how the Proxy changes the default behavior, you can always get the default behavior in Reflect. That is, reflect.fn represents the default behavior of fn in handler. Example usage:

var obj = new Proxy({}, {
  get: function (target, key, receiver) {
    console.log(`getting ${key}! `);
    // In the browser console, the get method prints the value by default
    // If reflect.get does not perform the default behavior, it will not print the value correctly and will print undefined instead
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {
    console.log(`setting ${key}! `);
    return Reflect.set(target, key, value, receiver); }}); obj.a =12;
console.log(obj.a)
Copy the code

4.4 Vue3.0 Bidirectional Binding

Data flow chart

4.4.1 Implementing Data Hijacking Reactive

First, use proxy to complete data hijacking. The code is as follows:

// Save reactive and raw data to prevent duplicate proxies as shown below
// let obj = {
// a:1
// }
// let p = new Proxy(obj, baseHandler
// let p1 = new Proxy(obj, baseHandler) // same data repeat Proxy
// let p2 = new Proxy(obj, baseHandler) // Same data repeat Proxy
// Raw => store the object after proxy responsively
let toProxy = new WeakMap(a);// Reactive => store the object before the proxy
let toRaw = new WeakMap(a);// Collect dependencies to create dependencies dynamically
function track(target, key){
  // Collect dependencies
}
// Update the view
function trigger(target, key, info){
  // Update the view
}
// **** Observer agent is different from vue2 ****
const baseHandler = {
    get (target, key) {
        console.log('Collect dependencies, initialize data')
        const res = target[key];
        // Collect dependencies to map effect keys to subscriptions
        // @todo 
         track(target, key);
        // Continue the proxy if it is an object
        return typeof res === "object" ? reactive(res) : res;  
    },
    set (target, key, newVal) {
        console.log('Data change')
        const info = {oldValue:target[key], newValue:newVal};
        // target[key] = newVal; The object cannot be set if it fails and an error will be reported if Reflect returns a value
        const res = Reflect.set(target, key, newVal );
        // Trigger private attributes to update such as arr length changes do not trigger updates
        if(target.hasOwnProperty(key)){
            // Notification update
            // @todo 
          trigger(target, key, info);
        }
        returnres; }}function reactive (target) {
    console.log(target,'Responsive data')
    // Return if it is not an object
    if(typeoftarget ! = ='object' && target === null) {return target;
    }
    // If the proxy data exists, no further proxy will be performed
    let proxy = toProxy.get(target)
    if(proxy){
        return proxy;   
    }
    // Return the object if it has already been proxied
    if(toRaw.has(target)){
        return target;   
    }
    // Create an observer
    const observed = new Proxy(target, baseHandler);
    toProxy.set(target, observed);  // The original object is the key proxy object is the value
    toRaw.set(observed, target);  // The original object is the key proxy object is the value
    console.log(observed,'Response completed')
    return observed;
}
Copy the code
4.4.2 Creating reactive dependencies

As we know above, all responsive data is called dependency in VUe2.0, and effect, or side effect, in VUe3.0, which can be simply understood as the side effect of data change —-> update view. In order for data to change and depend on methods that automatically perform view updates, you need to create reactive dependencies. The code is as follows:

// let obj = {name:'aa'}
// effect(()=>{
// console.log(obj.name) // Here get is triggered and dependencies are collected to map the key effect
// })
// obj.name = 'bb'
By default, the view update method is executed once to initialize the data and then execute the data change
// Create reactive dependencies and place them in an array of dependencies
let effectStack = []  // {name:effect()} // Collect dependencies
/ / {
// target:{
// key:[fn,fn,fn]
/ /}
// }
function effect (fn) {
    // The data change function executes as a reactive function
    let effect = createReactiveEffect(fn);
    effect(); // Execute once by default
    return effect;
}
function createReactiveEffect(fn){
    let effect = function(){  // effect
        return run(effect,fn);  // fn executes and exists on the stack
    };
    return effect;
}
function run (effect, fn) {
    try{
        effectStack.push(effect);
        return fn();
    }finally{ effectStack.pop(); }}Copy the code
4.4.3 Rely on collection and data update

Next we implement dependency collection and data update page changes, which is called effect. The code is as follows:

let targetMap = new WeakMap(a);// Collect dependencies
// Collect dependencies to create dependencies dynamically
function track (target, key) {  
    let effect = effectStack[effectStack.length- 1];
    if(effect){  // Create an association only when there is a corresponding relationship
        let depMap = targetMap.get(target);
        if(depMap === undefined){
            depMap = new Map(a); targetMap.set(target, depMap); }let dep = depMap.get(key);
        if(dep === undefined) {
            dep = new Set(a); depMap.set(key, dep); }if(! dep.has(effect)){ dep.add(effect) } } }// Update the view
function trigger (target, key, info) {
    
    const depMap = targetMap.get(target);
    
    if(! depMap){return;
    }
    const effects = new Set(a);if(key){
        let deps = depMap.get(key);
       
        if(deps){
            deps.forEach(effect= >{
                effects.add(effect)
            })
        }
    }
    effects.forEach(effect= > effect())
}
Copy the code
4.4.4 Complete code and Demo effect
<div id="box" style="margin-left: 20px;">
    <span id="app"></span> 
    <span id="add" style="margin-left: 10px; display: inline-block; width: 10px; height: 10px; cursor: pointer;">+</span>
</div>
<script src="./vue/vue3.js"></script>
<script>
    let ele = document.getElementById('app');
    let btn = document.getElementById('add');
    let o = {
        number: 1,}let reactiveData = reactive(o);
    effect((a)= >{
        console.log('The data has changed')
        ele.innerHTML = reactiveData.number
    })
    btn.addEventListener('click', () => {
        reactiveData.number += 1;
    },false)
</script>
Copy the code
// vue3.js
// Save reactive and raw data to prevent duplicate proxies as shown below
// let obj = {
// a:1
// }
// let p = new Proxy(obj, basehandler)
// let p1 = new Proxy(obj, basehandler)
// let p2 = new Proxy(obj, basehandler)
// Raw => store the object after proxy responsively
let toProxy = new WeakMap(a);// Reactive => store the object before the proxy
let toRaw = new WeakMap(a);let targetMap = new WeakMap(a);// Collect dependencies
let effectStack = []  // {name:effect()} // Collect dependencies
/ / {
// target:{
// key:[fn,fn,fn]
/ /}
// }
// Collect dependencies to create dependencies dynamically
function track (target, key) {  
    let effect = effectStack[effectStack.length- 1];
    if(effect){  // Create an association only when there is a corresponding relationship
        let depMap = targetMap.get(target);
        if(depMap === undefined){
            depMap = new Map(a); targetMap.set(target, depMap); }let dep = depMap.get(key);
        if(dep === undefined) {
            dep = new Set(a); depMap.set(key, dep); }if(! dep.has(effect)){ dep.add(effect) } } }// Update the view
function trigger (target, key, info) {
    
    const depMap = targetMap.get(target);
   
    if(! depMap){return;
    }
    const effects = new Set(a);if(key){
        let deps = depMap.get(key);
       
        if(deps){
            deps.forEach(effect= >{
                effects.add(effect)
            })
        }
    }
    effects.forEach(effect= > effect())
}

// The observer agent is different from vue2
const baseHandler = {
    get (target, key) {
        const res = target[key];
        // Collect dependencies to map effect keys to subscriptions
        track(target, key);
        return typeof res === "object" ? reactive(res) : res;  // Continue the proxy if it is an object
    },
    set (target, key, newVal) {
        const info = {oldValue:target[key], newValue:newVal};
        // target[key] = newVal; The object cannot be set if it fails and an error will be reported if Reflect returns a value
        const res = Reflect.set(target, key, newVal );
        // Notification update
        // Trigger private attributes to update such as arr length changes do not trigger updates
        if(target.hasOwnProperty(key)){
            trigger(target, key, info);
        }
        returnres; }}function reactive (target) {
    // Return if it is not an object
    if(typeoftarget ! = ='object' && target === null) {return target;
    }
    // If the proxy data exists, no further proxy will be performed
    let proxy = toProxy.get(target)
    if(proxy){
        return proxy;   
    }
    // Return the object if it has already been proxied
    if(toRaw.has(target)){
        return target;   
    }
    // Create an observer
    const observed = new Proxy(target, baseHandler);
    toProxy.set(target, observed);  // The original object is the key proxy object is the value
    toRaw.set(observed, target);  // The original object is the key proxy object is the value
    console.log(observed,'Response completed')
    return observed;
}

// let obj = {name:'aa'}
// effect(()=>{
// console.log(obj.name) // Here get is triggered and dependencies are collected to map the key effect
// })
// obj.name = 'bb'
By default, the view update method performs a data change before execution
function effect (fn) {
    // The data change function executes as a reactive function
    let effect = createReactiveEffect(fn);
    effect(); // Execute once by default
    return effect;
}
function createReactiveEffect(fn){
    let effect = function(){  // effect
        return run(effect,fn);  // fn executes and exists on the stack
    };
    return effect;
}
function run (effect, fn) {
    try{
        effectStack.push(effect);
        return fn();
    }finally{ effectStack.pop(); }}Copy the code

5. To summarize

Vue3.0 abandoned Object.defineProperty() in favor of proxy, and finally we summarize the differences between the two:

(1) defineProperty() can only listen on a certain attribute, but not on the whole object. Proxy listens on the whole object and returns a new object. For in, closure and other contents can be omitted to improve efficiency.

let obj1 = {
    a: 1.b: 2,}/ / proxy
let proxy1 = new Proxy(obj1, {
    set (target, key, value){
        console.log(`setting ${key}! `);
        return Reflect.set(target, key, value, receiver);
    },
    get (target, key){
        console.log(`getting ${key}! `);
        return Reflect.get(target, key, receiver); }})/ / Object. DefineProperty () method
function observe(data){
    if(! data ||typeofdata ! = ='object') {
        return;
    }
    // Retrieve all attributes to traverse
    Object.keys(data).forEach(function(key) {
        defineReactive(data, key, data[key]);
    });
}
function defineReactive(data, key, val){
    observe(val); // Listen for child attributes
    Object.defineProperty(data, key, {
        enumerable: true.configurable: true.get: function() {
            console.log(key + 'Value fetch')
            return val;
        },
        set: function(newVal) {
            console.log(key + 'Value Settings', val, '- >', newVal); val = newVal; }}); } observe(obj1);console.log(obj1.a);
console.log(obj1.b);
obj1.a = 12;
Copy the code

(2) Can listen to the array, no longer to separate the array to do specific operations.

The comparison code is as follows:

let arr = [1.2.3];
let p = new Proxy(arr, {
  get(target, key,) {
    console.log('Get array properties',target,key)
    return target[key];
  },
  set(target, key, value) {
    console.log('Set array Properties',key,+', '+target[key] + '- >' + value )
    target[key] = value;
    return true; }})console.log(p) // Proxy {0: 1, 1: 2, 2: 3}
p.push(4);  
// four steps occur :(1) get array properties (3) [1, 2, 3] push
// Get the array attribute (3) [1, 2, 3] length
// (3) set array property 3 NaN -->4
// (4) set the array attribute length NaN -->4
console.log('+ +') // Set the array property 0 NaN -->10
p[0] = 10;
console.log('-- -- -- -- -- -- -- -- -- -- -');
let arrObj = {
   b:1,}let bValue = arrObj.b;
Object.defineProperty(arrObj, "b", {
    enumerable: true.configurable: true.get: function() {
        let key = "b"
        console.log(key + 'Value fetch', bValue)
        return bValue;
    },
    set: function(newVal) {
        let key = "b"
        console.log(key + 'Value Settings -->', newVal);
        bValue = newVal;
        return bValue
    }
})
console.log(arrObj.b) / / 1
arrObj.b = [1.2.3];  // Set b value --> (1, 2, 3)
arrObj.b.push(4);  [1, 2, 3] [1, 2, 3
arrObj.b[0] = 10;  [1, 2, 3, 4]
Copy the code

The principle is always difficult to understand, the learning process is also very painful, stumbled after reading, we hereby summarize and share with you, there are incorrect expressions, I hope to correct more.


Git address: github.com/aicai0/vue3…

If you have any questions, welcome to discuss, if you are satisfied, please manually click “like”, thank you! 🙏

For more postures, please pay attention!!