vue data bidirectional binding principle summary plus complete code

Posted by benyhanna on Sun, 28 Nov 2021 12:47:16 +0100

Step by step implementation of bidirectional data binding

1. reduce() method of array

Application scenario: the initial value of the next operation, which depends on the return value of the last operation

(1) Cumulative calculation of array

Common implementation

const arr = [1,2,3,4,5,6]
 let total = 0;
 arr.forEach(item=>{
 total+=item;
})
 console.log(total);

Implemented through reduce

Basic knowledge

  • It is implemented using the reduce() method
  • Usage: array. Reduce (function, initial value)
  • Loop through the current array, focusing on "snowball"
    • Array. Reduce ((result of last calculation, item item item of current cycle) = > {}, 0)
    • Array. Reduce ((result of last calculation, item item item of current cycle) = > {return result of last calculation, item item item of current cycle}, 0)
    • const cumulative result = array. Reduce ((last calculated result, item item item of current cycle) = > {return last calculated result

code

const total = arr.reduce((val,item) => {
  return val + item;
},0)
console.log(total);
(2) Gets the value of an object property in a chain
const obj = {
  name:'xx',
  info:{
    address:{
      location:'Beijing Shunyi',
    }
  }
}

Common implementation

const location = obj.info.address.location
console.log(location)//Beijing Shunyi

Implemented through reduce - an array is initially given

Basic ideas

//First reduce
         The initial value is obj This object,
         Current item Item is info
         for the first time reduce The result is obj.info Object corresponding to the property of

//Second reduce
         The initial value is obj.info This object,
         Current item Item is address
         educe The result is obj.info.address Object corresponding to the property of

//Third reduce
         The initial value is obj.info.address This object,
         Current item Item is location
         educe The result is obj.info.address.location The value corresponding to the property of

code

const attrs = ['info','address','location']
const location =  attrs.reduce((newobj,key) => {
  return newobj[key]
},obj)
console.log(location)

The upgrade operation is implemented through reduce - a string is initially given and divided

code

const attrs = 'info.address.location'
const location = attrs.split('.').reduce((newobj,key) =>
 newobj[key]
 ,obj)

2. Publish subscribe mode

1, Code parsing

(1) Dep class
  • Responsible for dependency collection
    • First, there is an array to store all subscription information
    • Second, provide a method to append subscription information to the array
    • Third, provide a loop that triggers each subscription information in the array

code

//Collect dependencies / subscribers
class Dep{
  //The constructor() method is a special method for creating and initializing objects created in classes.
  // When the class is initialized, the constructor() method is called automatically, and it must use the exact name "constructor"
  constructor() {
    //This sub array is used to store the information of all subs cribers
    this.subs = []
  }

  //Add subs criber information to the sub array and accept an instance
  addSub(watcher){
    this.subs.push(watcher)
  }

  //Method of issuing notification
  notify(){
    this.subs.forEach((watcher) => watcher.update())
  }
}
(2) Watcher class
  • Responsible for subscribing to some events

code

//Subscriber's class
class Watcher{
 constructor(cb) {
   this.cb =cb//Mount cb on yourself
 }
  //Method of triggering callback
  update(){
    this.cb()
  }
}
//Create class
const w1 = new Watcher(() => {
  console.log('I'm the first subscriber')
})
// w1.update(); call this function to print that I am the first subscriber
const w2 = new Watcher(() => {
  console.log('I'm the second subscriber')
})


const dep = new Dep()
dep.addSub(w1)
dep.addSub(w2)
dep.notify()

2, Operation

(1) Describes how vue publish subscribe mode works
  • As long as we re assign the value to the data in vue, the assignment action will be monitored by vue, and then vue will notify each subscriber of the data change. Next, the subscriber (DOM element) will update its own class content according to the latest data
  • At the moment when the data is updated, update will be triggered, and the code written in it is the operation based on the new data

3. Use Object.defineProperty() to hijack data

(1) . hijack the value operation through get() - it is called getter, and the function name is get
(2) . hijack the assignment operation through set() - it is called setter, and the function name is set
 const obj = {
  name:'zs',
   age:20,
 }

 Object.defineProperty(obj,'name',{
   enumerable:true,//Current attribute, allowed to be looped for in
   configurable:true,//The current attribute can be configured, such as delete, and the attribute in the object can be deleted
   get(){
     console.log('Someone got it obj.name Value of')
     return 'I am not zs'
   },
   set(newval){
     console.log('Don't value',newval)
     dep.notify()
   }
 })
 //Value operation
 console.log(obj.name)
 //Assignment operation
 obj.name = 'ls'

Pre implementation of two-way binding paves the way for code learning
  • html
    • Implement getter and setter
    • Attribute agent
    • Data hijacking
    • Recursively add set and get for each
    • Solve the problem that there is no set or get after assignment
<body>
<div id="app">
  <h1>full name:{{name}}</h1>
</div>
<script src="05-vue.js"></script>
<script>
  const vm = new Vue({
    el:'#app',
    data:{
      name:'zs',
      age:20,
      info:{
        a:'a1',
        c:'c1'
      }
    }
  })
  console.log(vm.name);//Write vm.name as undefined here
  //To access here, you need to access vm.$data.name
  //Through the property proxy, you can access the properties using vm.name
  • js
    • this refers to the instance that is finally new
//Implement getter s in vue
class Vue{
  constructor(options) {
    this.$data = options.data
    //Call the method of data hijacking
    observe(this.$data)


    //Attribute agent
    Object.keys(this.$data).forEach((key) => {
      Object.defineProperty(this,key,{
        enumerable:true,
        configurable:'true',
        get() {
          return this.$data[key]
        },
        set(newvalue) {
          this.$data[key] = newvalue
        }
      })
    })
  }
}

//Define a method of data hijacking
function observe(obj) {

  //This is the termination condition of recursion
  if(!obj || typeof obj !== 'object') return//Is empty, not an object

  //Get each attribute of the current obj through object.keys(obj)
  Object.keys(obj).forEach((key) => {
    //The attribute value corresponding to the currently cycled key
    let value = obj[key]

    //The child node, value, is recursive
    observe(value)

    //You need to add getter s and setter s for the properties corresponding to the current key
    Object.defineProperty(obj,key,{
      enumerable:true,
      configurable:true,
      get() {
        console.log(`Someone got it ${key}Value of`);
        return value
      },
      set(newvalue) {
        value = newvalue;

        //Here, if the values a and C in the object are re assigned, the new
        //a and c will not have new get and set, so they need to call the new one again. What is modified here is info or an object vm.$data.info = {a:4,c:9},
        observe(value)
      }
    })
  })
}
  • Document fragmentation – to improve performance, the content changes, the page needs to be redrawn, the positioning position changes, and the page needs to be rearranged. In order to improve performance, add document fragmentation, that is, save the node in memory and modify it, so it will not be redrawn. After editing, re render the results to the page
  • Take out the data and put it into the text fragment. Operate it and return it to the vm
 //Call the function compiled by the template. The template structure is required for compilation. Get the data, transfer it to this, and call it when it is created
    Compile(options.el,this)


//Method for template compilation of HTML structure
function Compile(el,vm) {
  //Get the Dom element corresponding to el
  vm.$el = document.querySelector(el)
  //Get the element with id="demo" in the document: document.querySelector("#demo");
  //Create document fragments to improve the performance of Dom operations
  const fragment = document.createDocumentFragment()

  //take out
  while((childNode = vm.$el.firstChild)){
    fragment.appendChild(childNode)
  }

  //Perform template compilation
  replace(fragment)

  //Put it in
  vm.$el.appendChild(fragment)

  function replace(node) {
    //Define regular matching interpolation expressions
    const regMustache = /\{\{\s*(\S+)\s*\}\}/
    //\S matches any non white space characters
    //\s matches any white space characters, including spaces, tabs, page breaks, and so on.
    //() non blank characters are extracted and grouped with a small bracket

    //It is proved that the current node node is a text child node and needs regular replacement
    if(node.nodeType == 3){
      //Note: the text sub node is also a Dom object. If you want to get the string content of the text sub node, you need to call the textContent property to get it
      // console.log(node.textContent);
      //Conditions for terminating recursion
      return
    }
   //The proof is not a text node, but may be a Dom element, which needs recursive processing
    node.childNodes.forEach((child) => replace(child))
  }

  • Text sub nodes are regularly matched and replaced. Here, the data is rendered to the page
 if(node.nodeType == 3){
      //Note: the text sub node is also a Dom object. If you want to get the string content of the text sub node, you need to call the textContent property to get it
      // console.log(node.textContent);
      const text = node.textContent
      //Regular string matching and extraction
      const execRusult = regMustache.exec(text)//Is an array, the index 0 is {{name}}, the index 1 is name, and the exec () method is used to retrieve the matching of regular expressions in the string.
      if(execRusult){
        const value = execRusult[1].split('.').reduce((newobj,k) => newobj[k],vm)
        // console.log(value);
        node.textContent = text.replace(regMustache,value)
      }
  • Publish subscribe – when the data is updated, the page is rendered
    • Create two classes and create an instance of the watcher class
  //At this time, create an instance of the watcher class, save this method to the watcher, and call update to execute

        new Watcher(vm,execRusult[1],(newValue) => {
          node.textContent = text.replace(regMustache,newValue)
        })

//Dependent collected classes
class Dep{
  constructor() {
    //All watcher s should be stored in this array
    this.subs = []
  }
  //Like in the array, add the watcher method
  addSub(watcher){
    this.subs.push(watcher)
  }
  //The method responsible for notifying each watcher
  notify(){
    this.subs.forEach((watcher) => watcher.update())
  }
}
//Subscriber's class
class Watcher{
  //The cb callback function records the current watcher and updates its own text content
  //At the same time, you need to get the latest data. Therefore, during the new watcher, you need to pass it in to the vm
  //You should know that among the numerous data in the vm, the data is the data you currently need. During the new watcher period, specify the name of the data corresponding to the watcher
  constructor(vm,key,cb) {
    this.vm = vm
    this.key = key
    this.cb = cb
  }
  //The watcher instance needs the update function so that the publisher can notify us to update
  update(){
    this.cb()
  }
}
  • Store the watcher instance in the dep.subs array
get() {
        //As long as the following line is executed, the watcher instance of new just now is added to the dep.sub array
        Dep.target&&dep.addSub(Dep.target)
      }
      

    //The following three lines of code are responsible for saving the created watcher instance into the subs array of the dep instance
    Dep.target = this//Custom properties, watcher instance
    key.split('.').reduce((newobj,k) => newobj[k],vm)//The main purpose here is to execute this line of code, jump to get and return name and age,
    Dep.target = null
  
  • Unidirectional binding of data to view - displayed on the page after modification
 set(newvalue) {
        dep.notify()
        }
        
 update(){
    const value = this.key.split('.').reduce((newobj,k) => newobj[k],this.vm)
    this.cb(value)
  }

  • Realize one-way data binding of text box
//Judge whether the current node is an input field
    if(node.nodeType === 1&& node.tagName.toUpperCase() === 'INPUT'){
      //Get all attribute nodes of the current element
      const attrs = Array.from(node.attributes)
      const findResult = attrs.find((x) => x.name === 'v-model')
      if(findResult){
        //Get the value of the current v-model attribute v-model = 'name' v-model='info.a '
        const expStr = findResult.value
        const value = expStr.split('.').reduce((newobj,k) => newobj[k],vm)
        node.value = value
        //Create an instance of Watcher
        new Watcher(vm,expStr,(newValue) => {
          node.value = newValue
        })
      }
    }

  • Realize bidirectional data binding of text box
//Listen to the input event of the text box, get the latest value of the text box, and update the latest value to the vm
        node.addEventListener('input',(e) => {
          const keyArr = expStr.split('.')
          const obj = keyArr.slice(0,keyArr.length-1).reduce((newobj,k) => newobj[k],vm)
          obj[keyArr[keyArr.length-1]] = e.target.value
        })

Complete code

  • HTML
<div id="app">
  <h3 >full name:{{name}}</h3>
  <h3>Age is:{{age}}</h3>
  <h3>info.a The values are:{{info.a}}</h3>
  <div>name Value of:<input type="text" v-model="name"> </div>
  <div>info.a Value of:<input type="text" v-model="info.a"> </div>
</div>5
<script src="05-vue.js"></script>
<script>
  const vm = new Vue({
    el:'#app',
    data:{
      name:'zs',
      age:20,
      info:{
        a:'a1',
        c:'c1'
      }
    }
  })
  console.log(vm.name);//Write vm.name as undefined here
  //To access here, you need to access vm.$data.name
  //Through the property proxy, you can access the properties using vm.name
</script>
</body>
  • js
//Implement getter s in vue
class Vue{
  constructor(options) {
    this.$data = options.data
    //Call the method of data hijacking
    observe(this.$data)


    //Attribute agent
    Object.keys(this.$data).forEach((key) => {
      Object.defineProperty(this,key,{
        enumerable:true,
        configurable:'true',
        get() {
          return this.$data[key]
        },
        set(newvalue) {
          this.$data[key] = newvalue
        }
      })
    })

    //Call the function compiled by the template. The template structure is required for compilation. Get the data, transfer it to this, and call it when it is created
    Compile(options.el,this)
  }
}



//Define a method of data hijacking
function observe(obj) {

  //This is the termination condition of recursion
  if(!obj || typeof obj !== 'object') return//Is empty, not an object
  const dep = new Dep()

  //Get each attribute of the current obj through object.keys(obj)
  Object.keys(obj).forEach((key) => {
    //The attribute value corresponding to the currently cycled key
    let value = obj[key]

    //The child node, value, is recursive
    observe(value)

    //You need to add getter s and setter s for the properties corresponding to the current key
    Object.defineProperty(obj,key,{
      enumerable:true,
      configurable:true,
      get() {
        //As long as the following line is executed, the watcher instance of new just now is added to the dep.sub array
        Dep.target&&dep.addSub(Dep.target)
        return value
      },
      set(newvalue) {
        value = newvalue;
        dep.notify()
        //Here, if the values a and C in the object are re assigned, the new
        //a and c will not have new get and set, so they need to call the new one again. What is modified here is info or an object vm.$data.info = {a:4,c:9},
        observe(value)
      }
    })
  })
}


//Method for template compilation of HTML structure
function Compile(el,vm) {
  //Get the Dom element corresponding to el
  vm.$el = document.querySelector(el)
  //Get the element with id="demo" in the document: document.querySelector("#demo");
  //Create document fragments to improve the performance of Dom operations
  const fragment = document.createDocumentFragment()

  //take out
  while((childNode = vm.$el.firstChild)){
    fragment.appendChild(childNode)
  }

  //Perform template compilation
  replace(fragment)

  //Put it in
  vm.$el.appendChild(fragment)

  function replace(node) {
    //Define regular matching interpolation expressions
    const regMustache = /\{\{\s*(\S+)\s*\}\}/
    //\S matches any non white space characters
    //\s matches any white space characters, including spaces, tabs, page breaks, and so on.
    //() non blank characters are extracted and grouped with a small bracket

    //It is proved that the current node node is a text child node and needs regular replacement
    if(node.nodeType == 3){
      //Note: the text sub node is also a Dom object. If you want to get the string content of the text sub node, you need to call the textContent property to get it
      // console.log(node.textContent);
      const text = node.textContent
      //Regular string matching and extraction
      const execRusult = regMustache.exec(text)//Is an array, the index 0 is {{name}}, the index 1 is name, and the exec () method is used to retrieve the matching of regular expressions in the string.
      if(execRusult){
        const value = execRusult[1].split('.').reduce((newobj,k) => newobj[k],vm)
        // console.log(value);
        node.textContent = text.replace(regMustache,value)
        //At this time, create an instance of the watcher class, save this method to the watcher, and call update to execute

        new Watcher(vm,execRusult[1],(newValue) => {
          node.textContent = text.replace(regMustache,newValue)
        })
      }

      //Conditions for terminating recursion
      return
    }

    //Judge whether the current node is an input field
    if(node.nodeType === 1&& node.tagName.toUpperCase() === 'INPUT'){
      //Get all attribute nodes of the current element
      const attrs = Array.from(node.attributes)
      const findResult = attrs.find((x) => x.name === 'v-model')
      if(findResult){
        //Get the value of the current v-model attribute v-model = 'name' v-model='info.a '
        const expStr = findResult.value
        const value = expStr.split('.').reduce((newobj,k) => newobj[k],vm)
        node.value = value

        //Create an instance of Watcher
        new Watcher(vm,expStr,(newValue) => {
          node.value = newValue
        })

        //Listen to the input event of the text box, get the latest value of the text box, and update the latest value to the vm
        node.addEventListener('input',(e) => {
          const keyArr = expStr.split('.')
          const obj = keyArr.slice(0,keyArr.length-1).reduce((newobj,k) => newobj[k],vm)
          obj[keyArr[keyArr.length-1]] = e.target.value
        })
      }
    }


   //The proof is not a text node, but may be a Dom element, which needs recursive processing
    node.childNodes.forEach((child) => replace(child))
  }
}

//Dependent collected classes
class Dep{
  constructor() {
    //All watcher s should be stored in this array
    this.subs = []
  }
  //Like in the array, add the watcher method
  addSub(watcher){
    this.subs.push(watcher)
  }
  //The method responsible for notifying each watcher
  notify(){
    this.subs.forEach((watcher) => watcher.update())
  }
}


//Subscriber's class
class Watcher{
  //The cb callback function records the current watcher and updates its own text content
  //At the same time, you need to get the latest data. Therefore, during the new watcher, you need to pass it in to the vm
  //You should know that among the numerous data in the vm, the data is the data you currently need. During the new watcher period, specify the name of the data corresponding to the watcher
  constructor(vm,key,cb) {
    this.vm = vm
    this.key = key
    this.cb = cb
    //The following three lines of code are responsible for saving the created watcher instance into the subs array of the dep instance
    Dep.target = this//Custom properties
    key.split('.').reduce((newobj,k) => newobj[k],vm)
    Dep.target = null
  }
  //The watcher instance needs the update function so that the publisher can notify us to update
  update(){
    const value = this.key.split('.').reduce((newobj,k) => newobj[k],this.vm)
    this.cb(value)
  }
}

ye

Topics: Javascript Vue Vue.js