Hi, I’m Hui Ye and I’m so cute. This is my latest series on Vue responsive Principles. This article will help you unwrap the Vue responsive principles step by step. Because I am also writing this article in the process of trial and error, continuous learning improvement, so, this article is also very suitable for beginners like me. The same design philosophy of Vue is incremental enhancement.

The above link

(1) First know Vue response type

About get hijacking

In the previous article, we talked briefly about Vue’s non-intrusive key, which is Object.defineProperty.

We can use it to hijack the value of data fetching and setting.

However, when they are combined together, new problems can arise, as we can see from the following example:

let a = {name:'jack'};
Object.defineProperty(a,'name', {get(){
    return 'You've been hijacked! ';
  },
  set(newValue){
    console.log('You're setting a value' + newValue);
  }
})
a.name = 'bob';
console.log(a.name);  // You have been hijacked!
Copy the code

The console output is:

You're setting the value Bob you've been hijackedCopy the code

If a.name=’ Bob ‘, we set the value of variable A to Bob, but console.log(a.name) will trigger the get method by default, which will return a.name as you have been hijacked! .

So, you will find that no matter how much you change the value of A. name, it will be forcibly hijacked when it is finally fetched.

Variable transit

However, if we pass through a variable, we assign the new value to the variable when setting, and return the variable when getting the value, the above problem will not occur.

let a={name:'jack'}; let temp=null; DefineProperty (a,'name',{get(){return temp; }, set(newValue){temp = newValue; // Assign the new value to the transition variable}}) a.name=' Bob '; console.log(a.name); // bobCopy the code

Use closures for encapsulation

With this solution in place, we can further encapsulate Object.defineProperty ourselves by means of closures.

We defined a method called defineReactive, which basically defines the response to intercept the get and set (read and write) of an object.

function defineReactive(obj,key,val) {
  Object.defineProperty(obj,key,{
    // Enumerable, false by default
    enumerable:true.// Attribute descriptors can be changed or deleted. Default is false
    configurable:true.get(){
      return val;
    },
    set(newValue){ val=newValue; }})}Copy the code

Val now serves two purposes: as a passable default value and as a transition variable. We can use the defineReactive

let a={};
defineReactive(a,'name'.'jack');
console.log(a.name);   // jack
a.name='bob';
console.log(a.name);   // bob
Copy the code

How to bind deep objects

At this time, the new problem also immediately produced, we above the object is a layer, the data structure is very simple, but, the actual project of the object’s data structure will not be so simple, so, we also have to solve the deep object interception operation problem.

So, let’s say we’re given an object a.

let a = {
  b: {c:20}};Copy the code

Obviously, it has three levels of nesting.

We first bind defineReactive to its B property. The code at this point should be defineReactive(a,’b’). The third default parameter we can’t fill in, otherwise the value of b will be overwritten.

For example, we write the following code:

let a = {
  b: {c:20}}; defineReactive(a,'b'.10)
console.log(a); 
Copy the code

You can see that c is directly covered. Therefore, for deep objects, do not give default values, otherwise the object’s original attributes will be overwritten.

However, when we enter two parameters, we also find that val is undefined because there is no default value.

let a = {
  b: {c:20}}; defineReactive(a,'b')
console.log(a);     // undefined
Copy the code

Therefore, we need to control the number of entries in the defineReactive method, and if there are two entries (no default), we simply assign val to the object of the current layer.

Function defineReactive(obj,key,val) {return (arguments. Length ===2){val = obj[key]; } Object.defineProperty(obj,key,{ get(){ return val; }, set(newValue){ val = newValue; }})}Copy the code

DefineReactive (a,’b’) should now be done.

Observer

So, the problem now is that we need to go through every attribute of the object and bind it to defineReactive.

let a = {
  b:{
    c:20
  },
  d:10
};
Copy the code

We will now write an Observer class dedicated to traversing the properties of the current layer.

class Observer{
  constructor(obj){
    this.walk(obj);
  }
  walk(obj){
    let keys=Object.keys(obj);
    for(let i =0; i<keys.length; i++){ defineReactive(obj,keys[i]) } } }Copy the code

When creating a new Observer instance, the first layer of the incoming object is traversed in full, plus defineReactive.

For obvious effect, we print the current key value in the defineReactive method.

function defineReactive(obj,key,val) {
  console.log(key);
  // ...
}
Copy the code

Let’s instantiate a new Observer(a); Theta will output b and d, so b and D are now responsive.

New Observer(A); It does two main things:

new Observer(a); => defineReactive(a,'b') => defineReactive(a,'d')

Define define active(a,’b’); define define active(a,’b’); define define active(a,’b’); Check whether obj[key] is an object. If obj[key] is an object, then there is another layer to traverse.

function defineReactive(obj,key,val) {
  console.log(key);
  // Determine the current number of input parameters, if two directly return the current layer of the object
  if(arguments.length===2){
    val=obj[key];
    // A new line of code
    typeof val === 'object' && new Observer(val);
  }
  // ...
}
Copy the code

In this case, a kind of Observer => walk traverses the current layer => defineReactive => If is an object? => Observer => walk traverse current layer => defineReactive => If object? => Observer => walk through the current layer => defineReactive => omit…

After this loop, we can go through each object completely and bind defineReactive to it.

The overall call sequence is shown as follows:

Even if we changed to array, let a = [‘ a ‘, ‘b’, ‘c’, ‘d’, {e: {f: 100}}], also have no problem.

Interception of property additions

Now, when the object is initialized, all of our properties are ready to respond.

However, if we execute A.B.C ={m:10}, the change will not be triggered, so we need to add an Observer instantiation to the newly modified object in set as well.

Object.defineProperty(obj,key,{
  // ...
  set(newValue){
    val=newValue;
    typeof val === 'object' && newObserver(val); }})Copy the code

So the final code is:

function defineReactive(obj,key,val) {
  console.log(key);
  // Determine the current number of input parameters, if two directly return the current layer of the object
  if(arguments.length===2){
    val=obj[key];
    typeof val === 'object' && new Observer(val);
  }
  Object.defineProperty(obj,key,{
    // Enumerable, false by default
    enumerable:true.// Attribute descriptors can be changed or deleted. Default is false
    configurable:true.get(){
      return val;
    },
    set(newValue){
      val=newValue;
      typeof val === 'object' && newObserver(val); }})}// Iterate over all attributes of the current layer of the object and bind defineReactive
class Observer{
  constructor(obj){
    this.walk(obj);
  }
  walk(obj){
    let keys=Object.keys(obj);
    for(let i =0; i<keys.length; i++){ defineReactive(obj,keys[i]) } } }Copy the code

This article refer to

Vue source code parsing data responsive principle