Use vue's two-way binding to implement a todo-list sample code, vuetodo-list
Preface
I recently learned the basic principles of the vue framework and read some technical blogs and some simple implementations of the vue source code, provides a deeper understanding of data proxy, data hijacking, template parsing, variant array methods, and bidirectional binding. As a result, I tried to practice what I learned and use some basic principles of vue to implement a simple todo-list to complete bidirectional binding to deeply complex objects and listening to arrays, I am more impressed with the basic principles of vue.
Github address: todo-list
Learning Link
In the front row, I would like to thank the following articles for their great help in understanding the basic principles of vue!
Analyzes vue implementation principles and implements mvvm by DMQ by yourself
Understanding of early vue source code by Liang Shaofeng
Effect
Data proxy
1. Brief Introduction to data proxy
Under normal circumstances, we will write data in data, as shown below
var vm = new Vue({ el: '#app', data: { title: 'hello world' } methods: { changeTitle: function () { this.title = 'hello vue' } }})console.log(vm.title) // 'hello world' or 'hello vue'
If there is no data proxy and we want to modify the title in data, the changeTitle in methods can only be changedthis.data.title = 'hello vue'
, The following console can only be changedconsole.log(vm.data.title),
Data proxy is such a function.
2. Implementation Principle
By traversing the attributes in data, you can set getter and setter for each attribute through object. defineProperty (), and copy each attribute in data to an object at the same level as data.
(Corresponding to the sample code above)
Triggering the getter will trigger the corresponding property getter in data. triggering the setter will trigger the corresponding property setter in data to implement proxy. The implementation code is as follows:
Var self = this; // this Is A vue instance, that is, vmObject. keys (this. data ). forEach (function (key) {Object. defineProperty (this, key, {// this. title, that is, vm. title enumerable: false, retriable: true, get: function getter () {return self. data [key]; // trigger getter} corresponding to data [key], set: function setter (newVal) {self. data [key] = newVal; // triggers the setter of the corresponding data [key }});}
If you are not familiar with object. defineProperty, you can learn it in the MDN document (link ).
Bidirectional binding
- Data Change ---> View update
- View update (input and textarea) --> data changes
View update --> Data Change
Binding in this direction is relatively simple, mainly through event listening to change data. For example, input can listen to input events. Once an input event is triggered, data is changed. Next, let's take a look at it.Data Change ---> View update
Binding in this direction.
1. Data hijacking
Let's think about how to implement data changes and update the view corresponding to the bound data?
The answer is object. defineProperty, through object. defineProperty traversal settings this. all the attributes in data are notified to the corresponding callback function in the setter of each attribute. The callback function here includes the function re-rendered in the dom view and the callback function added using $ watch, in this way, we use object. defineProperty hijacks the data. When we assign a value to the data, suchthis.title = 'hello vue'
The setter function is triggered to trigger the dom view re-rendering function to implement data changes and update the corresponding view.
2. Publishing-subscription Mode
So the question is, how can we trigger all the callback functions bound to the data in setter?
Since there are more than one callback function bound to the data, we put all the callback functions in an array. Once the setter of the data is triggered, all the callback functions in the array are traversed, we call these callback functions subscriber. It is recommended that the array be defined in the upper-level scope closest to the setter function, as shown in the following instance code.
Object. keys (this. data ). forEach (function (key) {var subs = []; // place the array Object that adds all subscribers here. defineProperty (this. data, key, {// this. data. title enumerable: false, retriable: true, get: function getter () {console. log ('Access data... Lala ') return this. data [key]; // return the value of the corresponding data}, set: function setter (newVal) {if (newVal = this. data [key]) {return; // if the data does not change, the function ends and the following code is not executed} this. data [key] = newVal; // value subs for data reassignment. forEach (function () {// notify all subscribers in subs })}});}
Then again, how can we put all the callback functions bound to data in an array?
We can do this in getter. We know that as long as the data is accessed, the getter of the corresponding data will be triggered. Then we can set a global variable target first, if we want to add a subscriber (changeTitle function) to the title attribute of data, we can first set target = changeTitle, cache the changeTitle function in target, and then access this. the title triggers the getter of the title. In the getter, add the value of the global variable "target" to the subs array. After adding the global variable "target", set it to null to add other subscribers. The instance code is as follows:
Object. keys (this. data ). forEach (function (key) {var subs = []; // place the array Object that adds all subscribers here. defineProperty (this. data, key, {// this. data. title enumerable: false, retriable: true, get: function getter () {console. log ('Access data to Lala la') if (target) {subs. push (target);} return this. data [key]; // return the value of the corresponding data}, set: function setter (newVal) {if (newVal = this. data [key]) {return; // if the data does not change, the function ends and the following code is not executed} this. data [key] = newVal; // value subs for data reassignment. forEach (function () {// notify all subscribers in subs })}});}
The above code is simplified for ease of understanding. In fact, we write the subscriber as a constructor watcher to access the corresponding data and trigger the corresponding getter when instantiating the subscriber, for detailed code, read the DMQ manual MVVM implementation.
3. template Parsing
Through the above two steps, we have implemented that once the data changes, we will notify the corresponding subscribers bound to the data. Next we will briefly introduce a special subscriber, that is, the view update function, A view update function is added for almost every data, so let's take a look at the view update function.
If there is the following code, how can we parse it into the corresponding html?
<input v-model="title">
First, we will briefly introduce the usage of view update functions,
For example, the parsing commandv-model="title",v-on:click="changeTitle"
And replace {title} with the corresponding data.
Back to the above question, how do I parse the template? We only need to traverse all dom nodes, including its subnodes,
- If the node attribute contains v-model, the view update function sets the input value to the title value.
- If the node is a text node, the view update function first uses a regular expression to retrieve the value 'title' in braces, and then sets the value of the text node to data ['title'].
- If the node attribute contains v-on: xxxx, the view update function first obtains the event type as click with regular expression, and then obtains the value of this attribute as changeTitle. The Event Callback Function is this. methods ['changetitle'], and then listen to the node click event with addEventListener.
We need to know that the view update function is also the subscriber of the corresponding data attribute. If we do not know how to trigger the view update function, we can review the publishing-subscription mode above.
Some friends may have questions about how to change the title value of the following h1 node after the value of the input node changes? After traversing all nodes, if the node contains the v-model attribute, use addEventListener to listen to the input event. Once the input event is triggered, change the value of data ['title, the setter of the title is triggered to notify all subscribers.
Listen to array changes
Unable to monitor each array element
If we want to change the listening array, we may think of using object. defineProperty traverses each element of the array and sets setter, but this is not written in the vue source code, because defineProperty increases the complexity of the code and reduces the code execution efficiency for each array element.
Variant array method
Since defineProperty cannot monitor every element of an array, we can rewrite the array method (push, pop, shift, unshift, splice, sort, reverse) to change the array.
The vue file is written as follows:
Vue contains a set of Variation methods for the observed array, so they will also trigger view update. These methods are as follows:
- Push ()
- Pop ()
- Shift ()
- Unshift ()
- Splice ()
- Sort ()
- Reverse ()
The following is the first vue source code Learning Series II: How to monitor the instance code in an array change
Const aryMethods = ['push', 'pop', 'shift ', 'unshift', 'splice ', 'sort', 'reverse']; const arrayAugmentations = []; aryMethods. forEach (method) =>{// here is the prototype method of native Array let original = Array. prototype [method]; // defines the encapsulated methods such as push and pop on the attributes of the arrayAugmentations object. // Note: it is an attribute rather than a prototype attribute. arrayAugmentations [method] = function () {console. log ('I have been changed! '); // Call the corresponding native method and return the result return original. apply (this, arguments) ;};}); let list = ['A', 'B', 'C']; // point the prototype pointer of the array we want to listen to the empty array object defined above // do not forget that the attribute of this empty array defines the list of encapsulated push methods. _ proto _ = arrayAugmentations; list. push ('D'); // I have been changed! 4 // The list2 is not redefined as the prototype pointer, So let list2 = ['A', 'B', 'C'] is output normally; list2.push ('D'); // 4
Defects of the variant array method
Defects of the variant array method in the vue document
Due to JavaScript restrictions, Vue cannot detect the following changed Arrays:
- When you use indexes to directly set an item, such as vm. items [indexOfItem] = newValue
- When you modify the length of an array, for example, vm. items. length = newLength
The document also describes how to solve the above two problems.
Last
The above is your understanding of some basic vue principles. Of course there are still many shortcomings. please correct me. I used to learn the basic principles of the vue framework only to cope with interviews. But after learning the basic principles of these vue, I can understand the principle of the Framework in depth, you can effectively avoid some pitfalls that you will encounter in the future. Therefore, if you have time, you will continue to look at the basic principles of the framework.
The above is all the content of this article. I hope it will be helpful for your learning and support for helping customers.