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 => { // 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 => { // 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( && !== 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 && 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)=> { (node, newVal, attr) }) // Changes in monitor view ('input', (e) => { (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)=> { (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
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div > <form> <input type="text" v-model="username"> </form> <div> <span v-bind="username"></span> </div> <p v-bind="username"></p> </div> <script> 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 => { // 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 => { // 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( && !== 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 && 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)=> { (node, newVal, attr) }) // Changes in monitor view ('input', (e) => { (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)=> { (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' } }) // () </script> </body> </html>
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!