Touch hands to teach you to implement a simple vue and write observer

Posted by shenmue232 on Thu, 03 Feb 2022 05:42:15 +0100

Beep in advance

In the previous section, we talked about the principle of responsiveness, which is relatively general and difficult to understand, so we started writing this section directly. At the end of the chapter, I converted the observer object into a js version for you to test directly. You can copy and debug while understanding the next writing process

start

The detection of observer is an object or array, so we need to pass this parameter (named value) in at the beginning

 class ObserverNext {
  $value: any;
  constructor(value) {
  ...
  }

In the previous response principle, we said that we should deeply recursively analyze the data in this object (that is, the data option), so we also have to have a walk method

 class ObserverNext {
  $value: any;
  constructor(value) {
  ... 
  this.walk(value)
  }
  private walk(obj: Object | Array<any>) {
    for (const [key, val] of Object.entries(obj)) {
      if (typeof val == "object") {
        //Judge arrays and objects at the same time
        new ObserverNext(key,val, obj);
      }
    }
  }

Now we have completed deep recursion, but there is no analysis process, so we have to write another method called detect to analyze. Here, we use Proxy to monitor (analyze) the object

//detect private method in observer
 private detect(val: any, parent: any) {
    const dep = this.dep//Defined in constructor
    const proxy = new Proxy(val, {
      get(obj, property) {
        if (!obj.hasOwnProperty(property)) {
          return;
        }
        dep.depend(property);
        return obj[property];
      },
      set(obj, property, value) {
        obj[property] = value;
        dep.notify(property);
        return true;
      },
    });

  }

When the object get s the data, we collect the dependency. When the object set s the data, we notify the dependency update,
Moreover, the proxy should be available externally. Our solution here is to replace it directly on the parent object

   parent[key] = proxy;

So we have to pass in the parent and key from the beginning.
So our constructor becomes

 constructor(key,value, parent) {
    this.$key=key;
    this.$value = value;

    this.$parent = parent;
    this.dep = new Dep();
    
    this.walk(value);
    this.detect(value, parent);
  }

Now our observer has become

class ObserverNext {
  $value: any;
  $parent: any;
  $key:string
  dep: any;
  constructor(key,value, parent) {
    this.$key=key;
    this.$value = value;

    this.$parent = parent;

    this.dep = new Dep();

    //def(value, "__ob__", this);
    this.walk(value);
    this.detect(value, parent);
  }
  private walk(obj: Object | Array<any>) {
    for (const [key, val] of Object.entries(obj)) {
      if (typeof val == "object") {
        //Judge arrays and objects at the same time
        new ObserverNext(key,val, obj);
      }
    }
  }
  private detect(val: any, parent: any) {
    const dep = this.dep
    const key=this.$key
    const proxy = new Proxy(val, {
      get(obj, property) {
        if (!obj.hasOwnProperty(property)) {
          return;
        }
        dep.depend(property);
        return obj[property];
      },
      set(obj, property, value) {
        obj[property] = value;

        dep.notify(property);
        //if(parent.__ob__)parent.__ob__.dep.notify(key)

        return true;
      },
    });

    parent[key] = proxy;
  }
}

But now there is a bug. If the son object is updated, it will only notify its dependent collector (dep) to update, and the parent object will not perceive any exceptions!

So we have to notify the Observer class instance of the parent object to update
Therefore, we should record the observer instance in each object
It is roughly as follows:

constructor(key,value,parent){
...
def(value, "__ob__", this); 
//Equivalent to value__ ob__= this;
...
}

Then, in the set of proxy, we need to notify the parent object to update

set(){
if(parent.__ob__)parent.__ob__.dep.notify(key)
}

Final results

Our original version is typescript version, but in order to facilitate you to test directly in the console, I converted it to js version for you to debug

class Dep {
  constructor() {}
  depend() {
    console.log("Dependency collection");
  }
  notify() {
    console.log("Dependency update");
  }
}
function def(obj, key, val, enumerable=false) {
    Object.defineProperty(obj, key, {
      value: val,
      enumerable: !!enumerable,
      writable: true,
      configurable: true,
    });
  }
class ObserverNext {
  constructor(key, value, parent) {
    this.$key = key;
    this.$value = value;

    this.$parent = parent;

    this.dep = new Dep();
   
    def(value, "__ob__", this);
    this.walk(value);
    this.detect(value, parent);
  }
  walk(obj) {
    for (const [key, val] of Object.entries(obj)) {
      if (typeof val == "object") {
        //Judge arrays and objects at the same time
        new ObserverNext(key, val, obj);
      }
    }
  }
  detect(val, parent) {
    const dep = this.dep;
    const key = this.$key;
    const proxy = new Proxy(val, {
      get(obj, property) {
        if (!obj.hasOwnProperty(property)) {
          return;
        }
        dep.depend(property);
        return obj[property];
      },
      set(obj, property, value) {
        obj[property] = value;

        dep.notify(property);
        if (parent.__ob__) parent.__ob__.dep.notify(key);

        return true;
      },
    });

    parent[key] = proxy;
  }
}

const vm = {
  data: {
    attr1: {
      a: 1,
      b: 2,
      c: 3,
    },
    array: [1, 2, 3],
  },
};

new ObserverNext('data',vm.data,vm);
//test
//vm. data. Attr1 - > return dependency collection

file

#Touch hands to teach you to realize a simple vue (1) responsive principle
#Touch hands to teach you to implement a simple vue (2) start writing observer
#Touch hands to teach you to implement a simple vue (3) start writing dep and watcher

Topics: Vue