The principle of
As we all know, VUE is a MVVM framework, which can realize the bidirectional binding between View and model. Unlike Backbone, model changes need to be manually notified to view update. The principle of VUE implementation is to achieve data holding through Object.defineProperty. Define setters and notify views of updates when data changes. Here is the implementation schematic of VUE on the net:
Implementation effect
1, MVVM
$el, methods and $data are initialized in vue. Observer traverses the data of $data and holds it. Compile traverses all nodes under $EL, parsing instructions and value operations ({{}}). $data is iterated over and proxyed to $data via getters and setters for Object.defineProperty.
2, the Observer
Walk through the data, set the getter and setter with Object.defineProperty, and the setter knows that the data has changed, and then tells Wacher to update the view.
3, the Compile
Run through all the nodes under $el, parse instructions, value operations, etc., bind update functions for each node (compile). The node deploys the same class of attributes to the method ☺, which attributes the event to the method with the same subscriber. The update function is invoked when the updated subscription message is received. Also initialize the render view when adding subscribers.
4, Watcher
Watcher, as a subscriber, acts as a bridge between the Observer and Compile, including the update method, which calls the event update function bound in Compile to initialize and update the view.
The realization of the MVVM
The MVVM completes the initialization and calls Observer and compile. $data can be delegated to this.$data.attribute by this. Because a property can correspond to multiple directives, a _binding property is required to hold all subscribers of the property so that when the property changes, all subscribers can be fetched to update the view.
functionMVVM(options) {// Initialize this.$data = options.data;
this.$methods = options.methods;
this.$el= options.el; Watcher this._binding = {}; // call observer and compile this._observer(options.data); this._compile(); // this. XXX agent this.$data.xxx
this.proxyAttribute();
}
Copy the code
The realization of the Observer
The Observer iterates over $data, ensnars data changes through setters of Object.defineProperty, listens for data changes, fetches all subscribers for that property, and notifies the update function to update the view. Note: there are loops and the closures (getters and setters) rely on loops (values and keys), so use immediate execution functions to fix the problem.
MVVM.prototype._observer = function(data) {
var self = this;
for(var key in this.$data) {
if (this.$data.hasownProperty (key)) {// Initialize the corresponding subscriber container (array) this._binding[key] = {_directives: [], _texts: []};if(typeof this.$data[key] === "object") {
return this._observer(this.$data[key]); } var val = data[key]; // Execute the function immediately to get the correct loop item (function(value, key) {
Object.defineProperty(self.$data, key, {
enumerable: true,
configurable: true,
get: function() {
return value;
},
set(newval) {
if(newval === value) {
return; } value = newval; // Retrieve all subscribers corresponding to this property and notify view to update - propertyif(self._binding[key]._directives) {
self._binding[key]._directives.forEach(function(watcher) { watcher.update(); }, self); } // Retrieve all subscribers corresponding to this property and notify view to update - textif(self._binding[key]._texts) {
self._binding[key]._texts.forEach(function(watcher) { watcher.update(); }, self); }}}); })(val, key); }}}Copy the code
The realization of the Compile
Compile traverses all nodes, parses instructions, binds update function for each node, and adds subscribers. When the subscriber notifies the view of the update, the update function is called to realize the update of the view. Here, too, you need to use immediate execution functions to solve closure dependent looping issues. We also need to work out how to replace only the text that changes the innerText of a node if the innerText of the node depends on multiple attributes. For example, {{message}} : {{name}} has been compiled and parsed into “Welcome: Naruto”. If message is changed to “hello”, how can “Welcome: Naruto” be changed to “Hello: Naruto”?
MVVM.prototype._compile = function() {
var dom = document.querySelector(this.$el); var children = dom.children; var self = this; var i = 0, j = 0; Dom var updater = null; dom var updater = null;for(; i < children.length; i++) {
var node = children[i];
(functionVar text = node.innertext; var text = node.innertext; var matches = text.match(/{{([^{}]+)}}/g);if(matches && matches. Length > 0) {node.bindingAttributes = [];for(j = 0; j < matches.length; J++) {/ / data a property var attr = matches [j]. Journal of match (/ {{([^ {}] +)}} /) [1]. / / to bind and the node's data attribute node. The preservation bindingAttributes. Push (attr); (function(attr) {
updater = functionVar innerText = text.replace(new RegExp() var innerText = text.replace(new RegExp()){{" " + attr + "}}"."g"), self.$data[attr]); Eg :<div>{{title}}{{description}}</div>for(var k = 0; k < node.bindingAttributes.length; k++) {
if(node.bindingAttributes[k] ! == attr) {// Restore the text of the original unchanged attribute. InnerText = innertext.replace ({{" " + node.bindingAttributes[k] + "}}", self.$data[node.bindingAttributes[k]]); } } node.innerText = innerText; } self._binding[attr]._texts.push(new Watcher(self, attr, updater)); })(attr); Var attributes = node.getAttributenames ();for(j = 0; j < attributes.length; J ++) {var attribute = attributes[J]; // DOM attribute var domAttr = null; Var vmDataAttr = node.getAttribute(attribute);if(a/v - bind: [^ =] +) /. The test (attribute)) {/ / parsing v - bind domAttr = RegExp.The $1; // Update function updater =function(val) { node[domAttr] = val; _binding[vmDataAttr]._directives. Push (new watcher (self, vmDataAttr, updater))}else if(attribute === "v-model" && (node.tagName = 'INPUT' || node.tagName == 'TEXTAREA') {// parse v-model // update function updater =function(val) { node.value = val; _binding[vmDataAttr]._directives. Push (new watcher (self, vmDataAttr, Node.addeventlistener (node.addeventListener (node.addeventListener (node.addeventListener (node.addeventListener ("input".function(evt) {
var $el = evt.currentTarget;
self.$data[vmDataAttr] = $el.value;
});
} else if(a/v - on: [^ =] +) /. The test (attribute)) {/ / parsing v - on var event = RegExp.The $1;
var method = vmDataAttr;
node.addEventListener(event, function(evt) {
self.$methods[method] && self.$methods[method].call(self, evt); }); } } })(node); }}Copy the code
The realization of the Watcher
Watcher acts as a subscriber and Bridges the gap between the Observer and Compile. The Observer listens for data changes and tells Wathcer to update its view (calling Wathcer’s update method). Watcher then tells Compile to call the update function. Implement DOM updates. The initial rendering of the page is also given to Watcher (or Compile).
functionWatcher(vm, attr, cb) { this.vm = vm; // viewmodel this.attr = attr; // A watcher subscribes to a data attribute this.cb = cb; This.update (); this.update(); } Watcher.prototype.update =function() {// Tell the update function in comile to update dom this.cb(this.vm).$data[this.attr]);
}
Copy the code
All the code
Git address: github.com/VikiLee/MVV…
Credit: juejin. Cn/post / 684490…
Using the example
<! DOCTYPE html> <html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="view">
<div v-bind:id="id">
{{message}}:{{name}}
</div>
<input type="text" v-model="name"/>
<button v-on:click="handleClick"</button> </div> </body> <script SRC ="js/MVVM.js" type="text/javascript"></script>
<script>
var vue = new MVVM({
el: "#view",
data: {
message: "Welcome.",
name: "Naruto",
id: "id"
},
methods: {
handleClick: function() {
alert(this.message + ":" + this.name + ", click OK luffy will come out");
this.name = 'luffy'; }}})setTimeout(function() {
vue.message = "Hello";
}, 1000);
</script>
</html>
Copy the code