(Part 3) copy “Vue ecology” series “enumeration” and “bidirectional binding”

Time:2020-1-17

(Part 3) copy “Vue ecology” series “enumeration” and “bidirectional binding”

(Part 3) copy

This task

  1. Give the noun “ergodic” a knock
  2. Analyze defineproperty
  3. Realize data bidirectional binding of CC uue
  4. Warm up for the next proxy instead of defineproperty

1、 ‘foreach’ vs’ map ‘

Many articles have written about the difference between them

  1. Foreach does not return a value, map will return an array
  2. Map is good for compression, because there are only three letters after all

But these differences are only superficial. Let me show you an interesting one

<div id="boss">
   <div>1</div>
   <div>2</div>
   <div>3</div>
</div>
<script>
  let oD = document.getElementById('boss');  
  //Normal execution
  oD.childNodes.forEach(element => {console.log(element); });
  // wrong report
  oD.childNodes.map(element => { console.log(element); });
</script>

Ods.childnodes is not an array. It is still a pseudo array, but it can use foreach. The first reaction is whether the two traversal methods are different in the way of implementation. But I think I think I think I’m crooked. The answer is actually that when the pseudo array ods.childnodes is formed, a foreach is hung on me
Through the above questions, I have some thoughts

  1. Since map returns a new array, it means that its space complexity will be greater
  2. Some pseudo arrays provided by some systems will mount foreach but not map

To sum up, use foreach insurance!
But what about map?
1: The principle of slice is to put a new array in a loop;

let slice = Array.prototype.slice;
slice.call(oD.childNodes).map(()=>{})

2: The principle of extension operator is not the same, but it can take all elements out. Next, we will fight against it

[...oD.childNodes].map(()=>{})

2、 Extended operator

There are many ways to use this magic grammar. Let’s fight with each other
The following code will execute correctly. It is sure that there is no problem to put objects into objects

let obj = {a:1,b:2},
    result = {...obj};
console.log(result)

The following code will report an error because Iterable is missing

 let obj = {a:1,b:2,length:2},
     result = [...obj];
 console.log(result)

The reason is that the ‘extension operator’ doesn’t know how to extend it. We need to tell how to extend it properly
Symbol.iterator is the attribute of symbol, and the key of Iterable is it

let obj = { '0': 'a', '1': 'b', length: 2 };
      obj[Symbol.iterator] = function() {
        let n = -1,
            _this = this,
            len = this.length;
        //Must have return value
        //And the return value must be an object
        return {
          //Must have next
          next: function() {
            n++;
            if (n < len) {
              return {
                Value: "this [n], // the returned value, which can be controlled at will
                Done: false // true means the end, false means the continuation
              };
            } else {
              return {
                done: true 
              };
            }
          }
        };
      };

      result = [...obj];
      console.log(result);

The above method can meet my requirements, but I really can’t praise the writing method. There is too much code
So I recommend the second way to use genertor

let obj = { '0': 'a', '1': 'b', length: 2 };
      obj[Symbol.iterator] = function*() {
        let n = -1, len = this.length;
        while (len !== ++n) {
          yield this[n];
        }
      };
      result = [...obj];
      console.log(result);

The whole world is fresh

3、 Defineproperty

This magical property has done a lot of magic things. It can be said that if the current front-end can’t use it, it’s impossible to say

function
A property of the monitoring object, which can make corresponding value and assignment, belongs to ‘meta programming’
The first parameter is the monitoring object
The second parameter is key
The third parameter must be an object, which can also be understood as a config object
For example, obj.name will trigger the get function
Obj. Name = ‘Lulu’ this will trigger the set property, but note that this will not trigger get
All these actions can be monitored, so we can do it for the sake of hahahaha

let obj = {
    name: 'a'
  };
  function proxyObj(obj, name, val) {
    Object.defineProperty(obj, name, {
      Enumerable: true, // describes whether the attribute will appear in the traversal of for in or object. Keys()
      Configured: true, // describes whether the attribute is configured and whether it can be deleted
      get() {
        return val;
      },
      set(newVal) {
        val = newVal;
      }
    });
  }
  proxyObj(obj,'name',obj['name'])
  console.log((obj.name = 2));

shortcoming

  1. There is no way to assign values to yourself in set, or it will lead to a dead cycle
  2. You can only monitor the changes of objects, not arrays and basic types
  3. If the target is not an object, it will report an error… I don’t know why it should be designed like this
  4. This setting of methods on object is not appropriate. In the next chapter, we will talk about reflect.defineproperty

4、 Proxy $data to VM

In the current project, this. $data.xxx is needed to use the data in data. We can directly access this.xxx
cc_vue/src/index.js

constructor(options) {
    //1: no matter what you pass on, I'll put it in for future expansion;
    // ...
    -------New
    //2: Hang $data on the VM, and the user can get the value directly from this.xxx
    this.proxyVm(this.$data);
    -------New
    // end
    new Compiler(this.$el, this);
  }
/**
   *@ method delegates the value of an object to the target object
   *@ param {data} object to be proxied
   *To whom does @ param {target} proxy
   */
  proxyVm(data = {}, target = this) {
   //By default, it is hung on the instance of the frame
    for (let key in data) {
      Object.defineProperty(target, key, {
        enumerable: true,
        configurable: true,
        get() {
          return data[key];
        },
        set(newVal) {
          if (newVal !== data[key]) {
            data[key] = newVal;
          }
        }
      });
    }
  }

In this way, you can directly access this
We need to change the operation of template value. It’s very simple to remove $data
cc_vue/src/CompileUtil.js

getVal(vm, expression) {
    let result,
      __whoToVar = '';
    for (let i in vm.$data) {
      //Data will act as a proxy in the next period, and remove the attributes on the prototype
      let item = vm.$data[i];
      if (typeof item === 'function') {
        __whoToVar += `function ${i}(...arg){return vm['${i}'].call(vm,...arg)}`;
      } else {
        __whoToVar += `let ${i}=vm['${i}'];`;
      }
    }
    __whoToVar = `${__whoToVar}result=${expression}`;
    eval(__whoToVar);
    return result;
  },

5、 Add hijacking for all data

This is the core, so we separate a hijacking module directly
The current step only adds hijacking. Please refer to the next item for details about what to do after hijacking
cc_vue/src/Observer.js

class Observer {
  constructor(data) {
    //I'm just in charge of initialization
    this.data = data;
    this.observer(data);
  }
  /**
   *@ method observe the object
   *@ param {data} object to observe
   */
  observer(data) {
  //Loop out all the values of the object to monitor
    if (data && typeof data === 'object'&& !Array.isArray(data)) {
      for (let key in data) {
        this.defineReactive(data, key, data[key]);
      }
    }
  }
  /**
   *The @ method performs two-way binding, and the operation action after each value will be reflected here
   *@ param {obj} object to observe
   *@ param {key} object to observe
   *@ param {value} object to observe
   */
  defineReactive(obj, key, value) {
   //Because the data data can be very deep, it must be recursive
    this.observer(obj[key]);
    let _this = this;
    Object.defineProperty(obj, key, {
      Configured: true, // can be changed and deleted
      Enumerable: true, // enumerable
      get() {
        return value;
      },
      set(newVal) {
        if (value !== newVal) {
          //If the new value passed in by the user is an object, observe it again
          _this.observer(newVal);
          value = newVal;
        }
      }
    });
  }
}

Of course, you need to start this module in index
cc_vue/src/index.js

class C {
  constructor(options) {
    //1: no matter what you pass on, I'll put it in for future expansion;
    for (let key in options) {
      this['$' + key] = options[key];
    }

    //2: hijack operations on data
    new Observer(this.$data);
    // ....

6、 Add watch and DEP

‘subscription and Publishing’ is the core function of Vue. It’s a little bit tricky here. Let’s sort it out
Now the changes of data data have been hijacked. The ideas are as follows:

  1. I want to know what I want to do when each data changes, such as {{name}}. When name changes, I want to get the corresponding value of name again, and where to render to the page?
  2. For example, a variable has many places to render. What should I do

cc_vue/src/Watch.js
This class is very simple. It only implements two functions, such as queue and execution queue

export class Dep {
  constructor() {
    This. Subs = []; // put all subscribers here
  }
  /**
   *Add method to the subscription queue
   */
  addSub(w) {
    this.subs.push(w);
  }
  /**
   *@ method publishes information to inform all subscribers
   */
  notify() {
    this.subs.forEach(w => w.update());
  }
}

cc_vue/src/Watch.js
Observer, it’s just that he’s a little around

export class Watcher {
// VM example
//Expr executed expression
//CB callback function, that is, the method executed when the variable is updated
  constructor(vm, expr, cb) {
    this.vm = vm;
    this.expr = expr;
    this.cb = cb;
    //Take the current value here, and compare the oldvalue with each change in the future to prevent useless updates
    this.oldValue = this.getOld();
  }
  /**
   *Only the first value of @ method will call it, record the old value, and be subscribed
   */
  getOld() {
   //He will only be called once
   //Dep is a reference type, and its value can be passed
    Dep.target = this; // this refers to the watch itself
    //Get the current value of this value
    let value = CompileUtil.getVal(this.vm, this.expr.trim());
    //Empty after operation
    Dep.target = null;
    //Assign value to oldvalue
    return value;
  }
  /**
   *@ method update value
   */
  update() {
//Get the new value, compare it with each other, and update it if there is any change
    let newVal = CompileUtil.getVal(this.vm, this.expr.trim());
    if (newVal !== this.oldValue) {
    this.cb();
    }
  }
}

Dep.target = this; this is the finishing line, let’s use it

cc_vue/src/CompileUtil.js

//Add a watch when parsing the template
text(node, expr, vm) {
    let content = expr.replace(/\{\{(.+?)\}\}/g, ($0, $1) => {
      //Because the template is parsed only once, you don't have to worry about it being repeated
      new Watcher(vm, $1, () => {
       //The callback here is the specific update operation
        this.updater.textUpdater(node, this.getContentValue(vm, expr));
      });
      return this.getVal(vm, $1);
    });
    this.updater.textUpdater(node, content);
  },

Getcontentvalue gets all text information within an element
Some people will ask why we need to update all the text information instead of just getting the changed text. That’s because many times I’m very good at writing such code as < p > {a} — {B} < / P >. Then we can’t just change the appearance of B, because we operate the textcontent property of P tag

getContentValue(vm, expr) {
    return expr.replace(/\{\{(.+?)\}\}/g, ($0, $1) => {
      $1 = $1.trim();
      return this.getVal(vm, $1);
    });
  },

In the above code, when we parse the text, we put in a watch, which will execute the getoldvalue method in the moment when the watch is new, so we have the following code
cc_vue/src/Observer.js

defineReactive(obj, key, value) {
    this.observer(obj[key]);
    //1: when hijacking a value, create a dep instance
    let dep = new Dep();
    let _this = this;
    Object.defineProperty(obj, key, {
      configurable: true,
      enumerable: true,
      get() {
        //2: when getting the value, check whether there is target parameter on the dep class
        //This parameter is the watch class that we hang when we get oldval
        //If any, call to put the watch class into the subscriber
        Dep.target && dep.addSub(Dep.target);
        return value;
      },
      set(newVal) {
        if (value !== newVal) {
          _this.observer(newVal);
          value = newVal;
          //3: every time the data is updated, the publisher is executed
          dep.notify()
        }
      }
    });
  }

In fact, consider that dep and watch can also be written as a class, but they can be written as two more suitable design patterns

Experiment
Create a new second folder to detect bidirectional data binding
CC uue / use / 2: two way binding

 <div id="app">
          <p>n: {{n}} </p>
          <p>n+m: {{n+m}} </p>
      </div>
let vm = new C({
    el: '#app',
    data: {
      n: 1,
      m: 2
    }
  });
//Change the value of n every second. N is successful as long as it changes on the screen
  setInterval(() => {
    vm.n += 1;
  }, 1000);

Adjust the configuration of webpack

new HtmlWebpackPlugin({
      filename: 'index.html',
      Template: path. Resolve (_dirname, '.. / use / 2: two way binding / index. HTML'),

Interested friends can test the effect of my project,

end

This implementation is only a preliminary binding operation
Next episode:

  1. To realize the binding of proxy mode of vue3.0, I haven’t seen how vue3.0 is designed. First, implement it with my own understanding, and then learn their methods, which is also to cultivate my own thinking
  2. Enough space to write a simple Axios, easy to test code

We can all communicate, learn and progress together, and realize our self-worth as soon as possible!!

GitHub: no star yet. We look forward to your support
Personal technology blog: personal blog
More articles, list of articles written by UI library article address