preface
Recently, I have studied the basic principles of VUE framework, read some technical blogs and some simple implementation of VUE source code, and have a deeper understanding of data proxy, data hijacking, template parsing, variable array method, two-way binding. As a result, I tried to practice the knowledge I had learned, realized a simple todo-list with some basic principles of VUE, completed the bidirectional binding of deep complex objects and monitored the array, and deepened my impression on the basic principles of VUE.
Github address: todo-list
Fatdong1.github. IO /to…
Learning links
Thank you for the following article, which is very helpful for me to understand the fundamentals of VUE!
-
Analyze the implementation principle of VUE and implement MVVM by DMQ by yourself
-
Understanding of vUE early source code by Liang Shaofeng
Implementation effect
Data brokers
1. A brief introduction to the data broker
Normally, we would write data inside data, as shown below
var vm = new Vue({
el: '#app',
data: {
title: 'hello world'
}
methods: {
changeTitle: function (a) {
this.title = 'hello vue'
}
}
})
console.log(vm.title) // 'hello world' or 'hello vue'Copy the code
If there is no data broker and we need to change the title in data, ChangeTitle = ‘hello vue’ and console = console.log(vm.data.title).
2. Implementation principle
Copy every property in data to an object of the same class as data by iterating through properties in data, setting getters and setters for each property with Object.defineProperty ().
(Corresponding to the sample code above)
Firing the getter here will fire the getter for the property in data, and firing the setter here will fire the setter for the property in data, and implement the proxy. The implementation code is as follows:
var self = this; // This is the vue instance, i.e. Vm
Object.keys(this.data).forEach(function(key) {
Object.defineProperty(this, key, { / / this. The title, that is, the vm. The title
enumerable: false,
configurable: true.get: function getter (a) {
return self.data[key]; // Trigger the getter for data[key]
},
set: function setter (newVal) {
self.data[key] = newVal; // Fire the setter for data[key]}}); }Copy the code
If you are not familiar with Object.defineProperty, you can learn about it in the DOCUMENTATION (link) of MDN
Two-way binding
-
Data changes –> View updates
-
View update (Input, Textarea) –> Data changes
View update –> Data changes This direction of binding is relatively simple, mainly through the event listening to change the data, such as input can listen for input event, once the input event is triggered to change the data. Let’s focus on understanding data changes -> view updates the binding in this direction.
1. Data hijacking
Let’s think for a moment about how data changes so that the view of the bound data is updated.
DefineProperty iterates through all the properties in this.data, notifying the corresponding callback function in the setter for each property. The callbacks here include dom view rerenders, callbacks added with $watch, etc. So we hijacked the data via Object.defineProperty, and when we reassigned to the data, If this.title = ‘hello vue’, this will trigger setter functions, which will trigger dom view re-rendering functions, data changes, corresponding to the view update.
2. Publish and subscribe
So the question is, how do we fire all the callback functions that bind that data in the setter?
Since there is more than one callback bound to the data, we put all the callback functions in an array, and once we fire the setter for the data, we iterate through the array and fire all the callback functions in the array. We call these callback functions subscribers. Arrays are best defined in the nearest parent scope of the setter function, as shown in the example code below.
Object.keys(this.data).forEach(function(key) {
var subs = []; // Put an array here to add all subscribers
Object.defineProperty(this.data, key, { // this.data.title
enumerable: false.configurable: true.get: function getter () {
console.log('Access data la la la')
return this.data[key]; // Return the value of the corresponding data
},
set: function setter (newVal) {
if (newVal === this.data[key]) {
return; // If the data has not changed, the function ends, and the following code is not executed
}
this.data[key] = newVal; // Data is reassigned
subs.forEach(function () {
// Notify all subscribers in subs}}})); }Copy the code
How do you put all the callbacks to the bound data into one array?
We can do something in the getter, we know that whenever we access data, it will trigger the getter for that data, so we can set a global variable target, and if we want to add a subscriber to the title property of data, We can set target = changeTitle, cache the changeTitle function in target, and then call this.title to trigger the title getter, which adds the value of the global variable target to the subs array. Set the global variable target to NULL after the addition to add additional subscribers. Example code is as follows:
Object.keys(this.data).forEach(function(key) {
var subs = []; // Put an array here to add all subscribers
Object.defineProperty(this.data, key, { // this.data.title
enumerable: false.configurable: true.get: function getter () {
console.log('Access data la la 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 has not changed, the function ends, and the following code is not executed
}
this.data[key] = newVal; // Data is reassigned
subs.forEach(function () {
// Notify all subscribers in subs}}})); }Copy the code
The above code is simplified for ease of understanding. In fact, we write the subscriber as a constructor watcher, which accesses the corresponding data when instantiating the subscriber and triggers the corresponding getter. For detailed code, you can read DMQ’s diy implementation MVVM
3. Template parsing
Through the above two steps we have already achieved once the data changes, will inform the corresponding binding data subscribers, next we to introduce a specific subscriber, namely view update function, almost each data will add the corresponding view update function, so we to understand the simple view update function.
Suppose we had the following code, how would we parse it into the corresponding HTML?
<input v-model="title">
<h1>{{title}}</h1>
<button v-on:click="changeTitle">change title<button>Copy the code
View update function: {{title}}, {{title}}, {{title}}
Back to the question above, how do you parse templates? We just have to go through all the DOM nodes including their children,
-
If the node property 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 fetches the value ‘title’ inside the braces with a regular expression and then sets the value of the text node to data[‘title’].
-
If the node attribute contains V-on: XXXX, the view update function will use the regex to get the event type click, and then get the attribute value changeTitle, then the event callback will be this.methods[‘changeTitle’]. Then listen for the node click event with addEventListener.
It is important to know that the view update function is also a subscriber to the corresponding property of data. If you do not know how to trigger the view update function, you can look at the publish-subscribe pattern above.
If the value of the input node changes, the title value of the h1 node changes as well. After traversing all nodes, if the node has the attribute V-model, the input event is listened for with addEventListener. Once the input event is triggered, changing the value of data[‘title’] triggers the setter for title, notifying all subscribers.
Listening for array changes
You cannot monitor every array element
If we had our own implementation of listening for array changes, we would probably want to use Object.defineProperty to go through each element of the array and set the setter, but the vue source code does not say that. Because each array element defineProperty adds complexity to the code itself and reduces code execution efficiency.
Thanks to Ma63d for the comments below this article. This explanation is very detailed, so I will not repeat it here.
Variable array method
Since we can’t monitor every element of the array through defineProperty, we can override the array’s methods (push, POP, Shift, unshift, splice, sort, reverse) to change the array.
The VUE documentation reads as follows:
Vue contains a set of mutating methods that observe arrays, so they will also trigger view updates. These methods are as follows:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
Here is vUE early source code learning series 2: How to listen to an array changes in the example code
const aryMethods = ['push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'];
const arrayAugmentations = [];
aryMethods.forEach((method)=> {
// Here is the prototype method for the native Array
let original = Array.prototype[method];
// Encapsulate methods such as push and pop on properties of arrayAugmentations
// Note: attributes, not stereotype attributes
arrayAugmentations[method] = function () {
console.log('I'm changed! ');
// Call the corresponding native method and return the result
return original.apply(this.arguments);
};
});
let list = ['a'.'b'.'c'];
// Point the prototype pointer to the array we want to listen to to the empty array object defined above
// Don't forget that the empty array property defines methods such as push that we wrapped
list.__proto__ = arrayAugmentations;
list.push('d'); // I am changed! 4
// List2 is not redefined as a prototype pointer, so it prints normally
let list2 = ['a'.'b'.'c'];
list2.push('d'); / / 4Copy the code
To __proto__ unfamiliar small partner can go to see Wang Fuming’s blog, write very good.
Pitfalls of the mutated array method
A flaw in the variable array method in the VUE documentation
Due to JavaScript limitations, Vue cannot detect the following altered arrays:
When you set an item directly using an index, for example, 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.
The last
The above is my understanding of some basic principles of VUE, of course, there are many deficiencies, welcome to correct. Themselves also to cope with the interview didn’t go to learn the basic principle of vue framework, but simple after learning the basic principle of the vue, let me understand through in-depth study principle, framework can effectively avoid the hole, I will meet so, have the time to build, or would you look at the framework of the basic principle.