SoFunction
Updated on 2025-04-04

vue implements simple two-way data binding

Mainly implemented through data hijacking and publishing subscription

  • Bidirectional data binding When data is updated, the view can be updated. The data update of the view is that the model can be updated in reverse.

Composition description

  • Observe Listener Hijacks data, perceives data changes, issues notifications to subscribers, adds subscribers to subscribers in get
  • Dep Message Subscriber Stores subscribers, notifies subscribers to call update functions
  • Subscriber Wather takes out the model value and updates the view
  • The parser Compile parsing directive, updates template data, initializes the view, instantiates a subscriber, binds the update function to the subscriber, and can update the view twice when receiving notifications. For v-model, you also need to listen to input events to realize the data flow from the view to the model.

Basic structure

HTML templates

  <div >
    <form>
      <input type="text" v-model="username">
    </form>
    <p v-bind="username"></p>
  </div>
  • A root node#app
  • The form element contains input, and binds the data username using the v-model directive
  • Use v-bind binding number username on p element

MyVue class

Simple simulation of Vue class

Save the options options when instantiating. In addition, by obtaining the dom element, store it on $el

    class MyVue {
      constructor(options) {
        this.$options = options
        this.$el = (this.$)
        this.$data = 
      }
    }

Instantiate MyVue

Instantiate a MyVue, pass the option in, and specify the bound element el and data object data in the option

    const myVm = new MyVue({
      el: '#app',
      data: {
        username: 'LastStarDust'
      }
    })

Observe listener implementation

Hijacking data is to be able to perceive, issue notifications, and perform update views when modifying data.

    class MyVue {
      constructor(options) {
        // ...
        // Attributes of monitoring data        (this.$data)
      }
      // Recursively traverse all attributes of the data object and hijack the data attributes { username: 'LastStarDust'}      observable(obj) {
        // Obj is empty or not an object, do not do anything        const isEmpty = !obj || typeof obj !== 'object'
        if(isEmpty) {
          return
        }

        // ['username']
        const keys = (obj)
        (key =&gt; {
          // If the property value is an object, call it recursively          let val = obj[key]
          if(typeof val === 'object') {
            (val)
          }
          // (this.$data, 'username', 'LastStarDust')
          (obj, key, val)
        })

        return obj
      }

      // Data hijacking, modify the get and set methods of attributes      defineReactive(obj, key, val) {
        (obj, key, {
          enumerable: true,
          configurable: true,
          get() {
            (`take out${key}Attribute value: The value is${val}`)
            return val
          },
          set(newVal) {
            // No changes have occurred, no updates have been made            if(newVal === val) {
              return
            }
            (`Update properties${key}的The value is: ${newVal}`)
            val = newVal
          }
        })
      }
    }

Dep Message Subscriber

Store subscribers, when receiving notifications, take out subscribers and call subscribers' update method

    // Define the message subscriber    class Dep {
      // Static attribute, this is a globally unique Watcher, because there can only be one global Watcher at the same time      static target = null
      constructor() {
        //Storage subscribers         = []
      }
      // Add subscriber      add(sub) {
        (sub)
      }
      // notify      notify() {
        (sub =&gt; {
          // Call the subscriber's update method          ()
        })
      }
    }

Add message subscriber to data hijacking process

Add a subscriber for each attribute

      defineReactive(obj, key, val) {
        const dep = new Dep()
        (obj, key, {
          enumerable: true,
          configurable: true,
          get() {
            // When initialization, the property get() method will be triggered, and there will be a value here, and it will be stored as a subscriber. When triggering the property set() method, the notify method will be called            if() {
              ()
            }
            (`take out${key}Attribute value: The value is${val}`)
            return val
          },
          set(newVal) {
            // No changes have occurred, no updates have been made            if(newVal === val) {
              return
            }
            (`Update properties${key}的The value is: ${newVal}`)

            val = newVal
            ()
          }
        })
      }

Subscriber Watcher

Take data from the model and update the view

    // Define the subscriber class    class Wather {
      constructor(vm, exp, cb) {
         = vm // vm instance         = exp // The string value corresponding to the instruction, such as v-model="username", exp is equivalent to "username"         = cb // Back to function Called when updating view         = () // Add yourself to the message subscriber Dep      }

      get() {
        // Add the current subscriber as the globally unique Wather to the         = this
        // Get data and trigger the getter method of attribute        const value = .$data[]
        // After executing the Add to Message Subscription Dep, reset         = null
        return value
      }

      // Perform update      update() {
        ()
      }

      run() {
        // Extract the attribute value from the Model model        const newVal = .$data[]
        const oldVal = 
        if(newVal === oldVal) {
          return false
        }
        // Execute the callback function, pass the vm instance, new value, and old value in the past        (, newVal, oldVal)
      }
    }

Compile

  • parses template directives, replaces template data, and initializes the view;
  • Bind the corresponding nodes of the template instructions to the corresponding update function and initialize the corresponding subscriber;
  • Initialize the compiler, store the dom element corresponding to el, store the vm instance, and call the initialization method
  • In the initialization method, starting from the root node, all the child nodes of the root node are taken out and the nodes are parsed one by one
  • During the parsing of nodes
  • The parsing directive exists, takes out the binding value, replaces the template data, and completes the initialization of the first view.
  • Bind update function to the node corresponding to the instruction, and instantiate a subscriber Wather
  • For the v-model instruction, listen for the 'input' event, and implement view updates to update the model's data
    // Define the parser    // Analyze the instruction, replace the template data, initial view    // The template's instruction binding update function, when data is updated, update the view    class Compile {
      constructor(el, vm) {
         = el
         = vm
        ()
      }

      init(el) {
        (el)
      }
      compileEle(ele) {
        const nodes = 
		// traverse nodes for analysis        for(const node of nodes) {
		 // If there are child nodes, call recursively          if( &amp;&amp;  !== 0) {
            (node)
          }

          // Instruction v-model and is the label is the input label          const hasVmodel = ('v-model')
          const isInputTag = ['INPUT', 'TEXTAREA'].indexOf() !== -1
          if(hasVmodel &amp;&amp; isInputTag) {
            const exp = ('v-model')
            const val = .$data[exp]
            const attr = 'value'
            // The initial model value is pushed to the view layer and the view is initialized            (node, val, attr)
            // Instantiate a subscriber and bind the update function to the subscriber. In the future, the view can be updated.            new Wather(, exp, (newVal)=&gt; {
              (node, newVal, attr)
            })

            // Changes in monitor view            ('input', (e) =&gt; {
              (exp, )
            })
          }
		 
		 // Instruction time v-bind          if(('v-bind')) {
            const exp = ('v-bind')
            const val = .$data[exp]
            const attr = 'innerHTML'
            // The initial model value is pushed to the view layer and the view is initialized            (node, val, attr)
            // Instantiate a subscriber and bind the update function to the subscriber. In the future, the view can be updated.            new Wather(, exp, (newVal)=&gt; {
              (node, newVal, attr)
            })
          }
        }
      }
      // Update the model value to the view      modelToView(node, val, attr) {
        node[attr] = val
      }
      // Update the view value to the model      viewToModel(exp, val) {
        .$data[exp] = val
      }
    }

Complete code

&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
  &lt;title&gt;Document&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;div &gt;
    &lt;form&gt;
      &lt;input type="text" v-model="username"&gt;
    &lt;/form&gt;
    &lt;div&gt;
      &lt;span v-bind="username"&gt;&lt;/span&gt;
    &lt;/div&gt;
    &lt;p v-bind="username"&gt;&lt;/p&gt;
  &lt;/div&gt;
  &lt;script&gt;

    class MyVue {
      constructor(options) {
        this.$options = options
        this.$el = (this.$)
        this.$data = 

        // Attributes of monitoring data        (this.$data)

        // Compile node        new Compile(this.$el, this)
      }
      // Recursively traverse all attributes of the data object and hijack the data attributes { username: 'LastStarDust'}      observable(obj) {
        // Obj is empty or not an object, do not do anything        const isEmpty = !obj || typeof obj !== 'object'
        if(isEmpty) {
          return
        }

        // ['username']
        const keys = (obj)
        (key =&gt; {
          // If the property value is an object, call it recursively          let val = obj[key]
          if(typeof val === 'object') {
            (val)
          }
          // (this.$data, 'username', 'LastStarDust')
          (obj, key, val)
        })

        return obj
      }

      // Data hijacking, modify the get and set methods of attributes      defineReactive(obj, key, val) {
        const dep = new Dep()
        (obj, key, {
          enumerable: true,
          configurable: true,
          get() {
            // When initialization, the property get() method will be triggered, and there will be a value here, and it will be stored as a subscriber. When triggering the property set() method, the notify method will be called            if() {
              ()
            }
            (`take out${key}Attribute value: The value is${val}`)
            return val
          },
          set(newVal) {
            // No changes have occurred, no updates have been made            if(newVal === val) {
              return
            }
            (`Update properties${key}的The value is: ${newVal}`)

            val = newVal
            ()
          }
        })
      }
    }

    // Define the message subscriber    class Dep {
      // Static attribute, this is a globally unique Watcher, because there can only be one global Watcher at the same time      static target = null
      constructor() {
        //Storage subscribers         = []
      }
      // Add subscriber      add(sub) {
        (sub)
      }
      // notify      notify() {
        (sub =&gt; {
          // Call the subscriber's update method          ()
        })
      }
    }

    // Define the subscriber class    class Wather {
      constructor(vm, exp, cb) {
         = vm // vm instance         = exp // The string value corresponding to the instruction, such as v-model="username", exp is equivalent to "username"         = cb // Back to function Called when updating view         = () // Add yourself to the message subscriber Dep      }

      get() {
        // Add the current subscriber as the globally unique Wather to the         = this
        // Get data and trigger the getter method of attribute        const value = .$data[]
        // After executing the Add to Message Subscription Dep, reset         = null
        return value
      }

      // Perform update      update() {
        ()
      }

      run() {
        // Extract the attribute value from the Model model        const newVal = .$data[]
        const oldVal = 
        if(newVal === oldVal) {
          return false
        }
        // Execute the callback function, pass the vm instance, new value, and old value in the past        (, newVal, oldVal)
      }
    }

    // Define the parser    // Analyze the instruction, replace the template data, initial view    // The template's instruction binding update function, when data is updated, update the view    class Compile {
      constructor(el, vm) {
         = el
         = vm
        ()
      }

      init(el) {
        (el)
      }
      compileEle(ele) {
        const nodes = 
        for(const node of nodes) {
          if( &amp;&amp;  !== 0) {
            // Recursive call, compile child nodes            (node)
          }

          // Instruction v-model and is the label is the input label          const hasVmodel = ('v-model')
          const isInputTag = ['INPUT', 'TEXTAREA'].indexOf() !== -1
          if(hasVmodel &amp;&amp; isInputTag) {
            const exp = ('v-model')
            const val = .$data[exp]
            const attr = 'value'
            // The initial model value is pushed to the view layer and the view is initialized            (node, val, attr)
            // Instantiate a subscriber and bind the update function to the subscriber. In the future, the view can be updated.            new Wather(, exp, (newVal)=&gt; {
              (node, newVal, attr)
            })

            // Changes in monitor view            ('input', (e) =&gt; {
              (exp, )
            })
          }

          if(('v-bind')) {
            const exp = ('v-bind')
            const val = .$data[exp]
            const attr = 'innerHTML'
            // The initial model value is pushed to the view layer and the view is initialized            (node, val, attr)
            // Instantiate a subscriber and bind the update function to the subscriber. In the future, the view can be updated.            new Wather(, exp, (newVal)=&gt; {
              (node, newVal, attr)
            })
          }
        }
      }
      // Update the model value to the view      modelToView(node, val, attr) {
        node[attr] = val
      }
      // Update the view value to the model      viewToModel(exp, val) {
        .$data[exp] = val
      }
    }

    const myVm = new MyVue({
      el: '#app',
      data: {
        username: 'LastStarDust'
      }
    })

    // ()
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;

The above is the detailed content of vue implementing simple two-way data binding. For more information about vue implementing two-way data binding, please pay attention to my other related articles!