The target

  • masterObject.definePropertyThe use of
  • Master js observer mode
  • Implement the V-Model manually

1- Pre-knowledge

1. Object.defineProperty

  • DefineProperty defines a new property directly on an Object or modifies an existing property object.defineProperty.

  • Object attributes can also be modified and deleted when assigned directly, but defining attributes with Object.defineProperty() allows more precise control of Object attributes through descriptor Settings

  <script>
  var Person={}
  var reactiveName = "zhangsan"
  
  Object.defineProperty(Person, 'name', {
      / / can be configured
      configurable: true./ / can be enumerated
      enumerable: true.Accessor property 'get'
      // Method triggered when reading 'person. name'
      get () {
     
        console.log('get')
        return reactiveName
      },
      // Accessor property 'set'
      // Method triggered when modifying 'person. name'
      set (newValue) {
        console.log('set')
        reactiveName = newValue
       
      }
  })
  
  
  console.log(Person.name); // 'zhangsan'
  Person.name = 'lisi';
  console.log(Person.name); // 'lisi'
  </script>
Copy the code

2. Observer mode

<script>
    / / subscriber
    class Dep {
      constructor() {
        this.watchers = []
      }
      // Add an observer
      addWatcher (watcher) {
        this.watchers.push(watcher)
      }

      / / notice
      notify  () {
        this.watchers.forEach(watcher= > {
          watcher.update()
        })
      }
      
    }
    

    / / observer
    class Watcher {
      constructor(callback) {
        this.callback = callback
      }
      update () {
        console.log('update');
        this.callback(); }}// Create a subscriber
    const dep = new Dep();
    // Create an observer
    const watcher1 = new Watcher(() = > console.log('watcher1'));
    const watcher2 = new Watcher(() = > console.log('watcher2'));
    // Add an observer
    dep.addWatcher(watcher1)
    dep.addWatcher(watcher2)
    // Trigger notification
    dep.notify();
</script>
Copy the code

3. Summary

  • Object.definePropertyA property used to define an object that can be configured with getter and setter accessors
  • The observer mode has two subscribers and an observer object, and the subscriber can collect the observer and trigger the observer to perform the update operation at the appropriate time
  • The V-Model is implemented by collecting observers in the GET method and triggering updates in the set method

2 – v – model

1. Use a Vue invocation method

  <div id="app">
    <input type="text" v-model="msg" />
  </div>

  <script>
  const vm = new MyVue({
    el: '#app'.data: {
      msg: 'abc'}})</script>
Copy the code

So we’re going to create MyVue objects

<script>
  class MyVue {
    constructor({el, data}) {
      this.container = document.querySelector(el);
      this.data = data; }}</script>
Copy the code

2. Initialize data

  • Define each attribute in data using Object.defineProperty
<script>
  class MyVue {
    constructor({el, data}) {
      this.container = document.querySelector(el);
      this.data = data;
      
      // Initialize data
      this.initData(this.this.data)
    }
    
    initData(vm, data) {
      for (let key in data) {
        let initVal = data[key];

        Object.defineProperty(vm, key, {
          get() {
            return initVal
          },
          set(val){ initVal = val; }})// Call initData recursively to determine if it is an object
        if (Object.prototype.toString.call(data[key]) === "[object Object]") {
          this.initData(vm[key], data[key])
        }
      }
    }
  }
</script>
Copy the code
<script>
  const vm = new MyVue({
    el: '#app'.data: {
      msg: 'abc'}});console.log(vm.msg) // abc
</script>
Copy the code

3. Initialize the V-Model form

<script>
	class MyVue {
      constructor({ el, data }) {
        this.container = document.querySelector(el);
        this.data = data;

        // Initialize data
        this.initData(this.this.data)

        // Initialize the V-Model form
        this.initVModel();
      }
      initVModel () {
        // Get all the V-Model elements
        const nodes = this.container.querySelectorAll('[v-model]');

        nodes.forEach(node= > {
          const key = node.getAttribute('v-model');
          
          // Initialize the assignment
          node.value = this[key];

          // Listen for input events
          node.addEventListener('input'.ev= > {
            this[key] = ev.target.value;
          }, false)}); }}</script>  
Copy the code

The above code can only fetch the first layer of data in the data, if it is bound like this:

<input type="text" v-model="obj.a" />
Copy the code

This [‘obj. A ‘] = ‘obj. A ‘;

getData(str) {
  const arr = str.split('. '); // ["obj", "a"]

  const res = arr.reduce((target, item) = > {
    // Target equals data for the first pass
    return target[item] // Target = target[item] ===> data.obj
    // Target equals data.obj for the second pass
    // Target equals data.obj.a after return
  }, this)

  return res;
},	
initVModel () {
  // Get all the V-Model elements
  const nodes = this.container.querySelectorAll('[v-model]');

  nodes.forEach(node= > {
    const key = node.getAttribute('v-model');

    // Initialize the assignment
    // node.value = this[key]; // If key is "obj
    node.value = this.getData(key); 

    // Listen for input events
    node.addEventListener('input'.ev= > {
      // this[key] = ev.target.value; // Also handle strings whose key is "obj.a"
      const arr = key.split("."); // ["obj", "a"]

      // If arr has only one element, assign it directly
      if (arr.length === 1) {
        this[key] = node.value;
        return;
      }
	
      // Get the penultimate object (for 'obj.a', get this.obj)
      const res = this.getData(key.substring(0, key.lastIndexOf("."))) // this.obj
	
      // Assign the last level (this.obj.a = node.value)
      res[arr[arr.length - 1]] = node.value;
    }, false)}); },Copy the code

At this point, the view change triggers the model change, and then the model change triggers the view change

4. Model changes trigger view updates

1. Introduce observer mode

	/ / subscriber
    class Dep {
      constructor() {
        this.watchers = []
      }
      // Add an observer
      addWatcher (watcher) {
        this.watchers.push(watcher)
      }

      / / notice
      notify  () {
        this.watchers.forEach(watcher= > {
          watcher.update()
        })
      }
      
    }
    

    / / observer
    class Watcher {
      constructor(callback) {
        this.callback = callback
      }
      update () {
        console.log('update');
        this.callback(); }}Copy the code

2. noticeTrigger time

Model-driven view updates, where dom updates are triggered when vm. MSG is modified (e.g. Vm. MSG =”123″). So we need to call dep.notify() in the set method.

// Initialize data
initData(vm, data) {
  for (let key in data) {
    let initVal = data[key];
    //++++++++++++++++++++++++++++++
    const dep = new Dep(); 
    //++++++++++++++++++++++++++++++
    Object.defineProperty(vm, key, {
      get() {
        return initVal
      },
      set(val) {
        initVal = val;
        //++++++++++++++++++++++++++++++
        // Notify the view of updates
        dep.notify();
        //++++++++++++++++++++++++++++++}})// Call initData recursively to determine if it is an object
    if (Object.prototype.toString.call(data[key]) === "[object Object]") {
      this.initData(vm[key], data[key])
    }
  }
}
Copy the code

3. Create an observer

The observer needs to update the DOM, so the observer needs to be created when the V-Model form is initialized

initVModel () {
  // Get all the V-Model elements
  const nodes = this.container.querySelectorAll('[v-model]');

  nodes.forEach(node= > {
    const key = node.getAttribute('v-model');

    // Initialize the assignment
    // node.value = this[key]; // If key is "obj
    node.value = this.getData(key); 
    
    //++++++++++++++++++++++++++++++
    // Create an observer
    const watcher = new Watcher(() = > {
      node.value = this.getData(key)
    })
    //++++++++++++++++++++++++++++++
    
    // Listen for input events
    node.addEventListener('input'.ev= > {
      // this[key] = ev.target.value; // Also handle strings whose key is "obj.a"
      const arr = key.split("."); // ["obj", "a"]

      // If arr has only one element, assign it directly
      if (arr.length === 1) {
        this[key] = node.value;
        return;
      }
	
      // Get the penultimate object (for 'obj.a', get this.obj)
      const res = this.getData(key.substring(0, key.lastIndexOf("."))) // this.obj
	
      // Assign the last level (this.obj.a = node.value)
      res[arr[arr.length - 1]] = node.value;
    }, false)}); },Copy the code

4. Add observers

Once an observer is created, it should be added to the subscriber immediately, but how will the subscriber know that the observer has been created?

  • At this point we get the data in the observer constructor and execute itObject.definePropertyGet method defined. So we add an observer to the get method.
// Pass this and key to create an observer
const watcher = new Watcher(this, key, () = > {
  node.value = this.getData(key)
})

/ / observer
class Watcher {
  constructor(vm, key, callback) {
    this.callback = callback;
    // Get data and trigger get
    vm.getData(key);
  }
  update () {
    console.log('update');
    this.callback(); }}// Add observer to get method
get() {
	dep.addWatcher(watcher)
	return initVal
},
Copy the code

5. Assign the observer instance to an attribute of the Dep

The watcher in dep.addWatcher(watcher) above does not exist. We need to assign the observer instance to an attribute of the DEP in the observer constructor before fetching data

/ / observer
class Watcher {
  constructor(vm, key, callback) {
    this.callback = callback;
    
    // Assign the observer instance to the Dep target property
    Dep.target = this;
    // Get data and trigger get
    vm.getData(key);
  }
  update () {
    console.log('update');
    this.callback(); }}/ / get methods
get() {
	dep.addWatcher(Dep.target)
	return initVal
},
Copy the code

6. Avoid adding observers repeatedly

Dep. AddWatcher (dep.target) was executed multiple times, so there were multiple observers in the DEP Watchers array, so the update method was executed multiple times. The solution is to retrieve the data and reset dep.target.

/ / observer
class Watcher {
  constructor(vm, key, callback) {
    this.callback = callback;
    
    // Assign the observer instance to the Dep target property
    Dep.target = this;
    // Get data and trigger get
    vm.getData(key);
    // Reset dep. target to avoid adding observers repeatedly after retrieving data
    Dep.target = null;
  }
  update () {
    console.log('update');
    this.callback(); }}/ / get methods
get() {
	Dep.target && dep.addWatcher(Dep.target)
	return initVal
},
Copy the code

7. Data does not need to be updated if it is set to the same value multiple times

set(val) {

  // If the values are the same, the update will not be triggered
  if (initVal === val) {
    return;
  }

  initVal = val;

  dep.notify(); // Notify the view of updates
}
Copy the code

The complete code

/ / subscriber
class Dep {
  constructor() {
    this.watchers = []
  }
  // Add an observer
  addWatcher(watcher) {
    this.watchers.push(watcher)
  }

  / / notice
  notify() {
    this.watchers.forEach(watcher= > {
      watcher.update()
    })
  }

}


/ / observer
class Watcher {
  constructor(vm, key, callback) {
    this.callback = callback

    // Second thing, pass the current instance to the get method
    Dep.target = this;

    // First thing, get the current data
    const val = vm.getData(key)

    To prevent repeated additions of observers, clear immediately after the addition
    Dep.target = null;
  }
  update() {
    console.log('update');
    this.callback(); }}class MyVue {
  constructor({ el, data }) {
    this.container = document.querySelector(el);
    this.data = data;

    // Initialize the data
    this.initData(this.this.data)

    // Initialize the V-Model form
    this.initVModel()
  }

  initData(vm, data) {
    for (let key in data) {
      let initVal = data[key];
      const dep = new Dep();
      Object.defineProperty(vm, key, {
        get() {
          // As soon as we get the data, we enter the get method and add the observer
          Dep.target && dep.addWatcher(Dep.target)
          return initVal
        },
        set(val) {

          // If the values are the same, the update will not be triggered
          if (initVal === val) {
            return;
          }


          // console.log(key + 'modified data ')
          initVal = val;

          dep.notify(); // Notify the view of updates}})// Call initData recursively to determine if it is an object
      if (Object.prototype.toString.call(data[key]) === "[object Object]") {
        this.initData(vm[key], data[key])
      }
    }

  }

  initVModel() {
    // Get all the V-Model forms
    const nodes = this.container.querySelectorAll('[v-model]')
    // console.log(nodes)

    nodes.forEach(node= > {
      // Get the value of the V-model attribute
      const key = node.getAttribute('v-model')


      // Create the form as an observer
      new Watcher(this, key, () = > {
        node.value = this.getData(key)
      })



      // console.log(123, key, this[key])
      const val = this.getData(key)
      // console.log(234234, val)

      // Assign a value to the form
      node.value = val

      // View-driven data update: listens for form events and modifies data.
      node.addEventListener('input'.() = > {
        // this[key] = node.value // key === "obj.b"

        const arr = key.split("."); // ["obj", "b"]

        // If arr has only one element, assign it directly
        if (arr.length === 1) {
          this[key] = node.value;
          return;
        }

        // let res = this.getData(key) // this.obj.b ===> 2
        const res = this.getData(key.substring(0, key.lastIndexOf("."))) // this.obj

        // res = node.value;
        res[arr[arr.length - 1]] = node.value; })})}// Pass in a string of the form 'A.B.C' to get the value of this.a.b.c
  getData(str) {
    const arr = str.split('. '); // ["obj", "a"]

    const res = arr.reduce((target, item) = > {
      // Target equals data for the first pass
      return target[item] // Target = target[item] ===> data.obj
      // Target equals data.obj for the second pass
      // Target equals data.obj.a after return
    }, this)

    returnres; }}Copy the code

conclusion

  • Object.defineProperty
  • Observer model
  • V-model is implemented manually
    • throughObject.definePropertyDefine all attributes of data, collect the observer in the GET method, and trigger the observer to update the DOM in the set method