Understand the Vue implementation principle and implement a simple Vue framework

Source: Internet
Author: User

Reference:
Anatomy of the Vue implementation principle-how to implement two-way binding MVVM
Vue.js Source (1): The back of Hello world
Vue.js Official Project

All the code in this article can be found on git.

In fact, the JS I study is not too deep, used many times, but only to realize the function will forget. Recently JS is really too fire, from the front to the back end, the application more and more extensive, various frameworks endless, can not help but also want to catch the tide.
Vue is a library of front-end building data-driven web interfaces in recent years, with the main feature being responsive data binding, which differs from previous imperative usages. That is, in the process of Var a=1, the process of intercepting ' = ', in order to realize the update data, the Web view also automatically synchronizes the updated function. You do not need to explicitly use the data to update the view (imperative). This usage I was first seen in VC MFC, the control bound variable, modify the value of the variable, the input box also changes synchronously.
Vue's official documents, on-line analytic articles are very detailed, but for the purpose of learning, or understand the principle, the realization of their own memory deep, but also can learn some of the knowledge of JS. In this line, be sure to WTFC (Write the fucking Code).

First, thinking design

In fact, the thinking here is to read a few articles, read some of the source to fill up, so some places will have the meaning of God's perspective. But this process is necessary, in the future there will be a problem with the direction of thinking.
Let's look at what we want to achieve and what we have now:
As follows:

Use the Vue framework code as follows:

<! DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>MVVM</title></head><body><script src="Src/vue.js"></script><div id="msg">{{B.C}} This is plain text {{B.c+1+message}} This is plain text<p>{{Message}}</P>    <p><input type="text" v-model="message"/></P>    <p>{{Message}}</P>    <p><button type="button" v-on:click="clickbtn (Message)" >Click Me</button></P></div><script> var vm = new Vue ({el:"#msg", data:{b:{C:1
      }, message:"Hello World" }, methods:{clickbtn:Fu Nction(message){ vm.message = "clicked"; }        }    });    </script></body></html>

Then we also know a condition that Vue's official document says:

Passing a normal object to the Vue instance as its data option, Vue.js will traverse its properties and use Object.defineproperty to convert them to getter/setter. This is a ES5 feature and cannot be patched, which is why vue.js does not support IE8 and lower versions.

What do we need to do with this feature for this purpose?

    1. First, we need to use Object.defineproperty, the object to be observed, into getter/setter, in order to intercept object assignment and value operation, called Observer;
    2. It is necessary to parse the DOM, extract the instructions and placeholders, and assign different operations, called compiler;
    3. It is necessary to connect the analytic results of compile with the objects observed by observer, to establish a relationship, to receive notification when observer observes the change of object data, and to update the DOM, called Watcher;
    4. Finally, a public entry object is required to receive the configuration and coordinate the above three, called Vue;
II. realization of Observer1. Conversion Getter/setter

Originally thought to be simple to implement, the result is only converted to getter and setter encountered a lot of problems. The original to JS really is only know a little fur ah ...

Start the Observer.js code as follows:

/** Observer is the input of the plain object processing, the use of object.defineproperty conversion to getter and setter, so that the assignment and the value of interception this is the basis of the Vue response Framework * / function isobject(obj){    returnObj! =NULL&&typeof(obj) = =' object ';} function isplainobject(obj){    return Object. prototype.tostring (obj) = =' [Object Object] ';} function observer(data){    if(!isobject (data) | |!isplainobject (DATA)) {return; }return NewObserver (data);}varObserver = function(data){     This. data = data; This. Transform (data);};o Bserver.prototype.transform = function(data){     for(varKeyinchData) {varValue = Data[key];Object. DefineProperty (data,key,{Enumerable:true, Configurable:true, Get: function(){Console.log ("Intercept get:"+key);returnValue }, set: function(newval){Console.log ("Intercept set:"+key);if(newval = = value) {return;            } Data[key] = newval; }        });//recursive processing         This. Transform (value); }};

Index.html:

<script src="Src/observer.js"></script><div id="msg">    <p>{{Message}}</P>    <p><input type="text" v-model="message"/></P>    <p>{{Message}}</P>    <p><button type="button" v-on:click="clickbtn">Click Me</button></P></div><script> var a = {b:{c:1}, D:2 };    Observer (a); A.D = 3;    </script>

The browser executes a direct dead loop stack Overflow, the problem is in the SET function, there are two problems:

set:function(newVal){    console.log("intercept set:"+key);    if(newVal == value){        return;    }    //这里,通过data[key]来赋值,因为我们对data对象进行了改造,set中又会调用set函数,就会递归调用,死循环    //而上面本来用来判断相同赋值不进行处理的逻辑,也因为value的值没有改变,没有用到。很低级的错误!    data[key] = newVal;}

Modify to value = newval OK? Why this can be modified, because the existence of the JS scope chain, value for this anonymous object, like the existence of global variables, in the set after modification, in the get can also return the modified value.

But just this is not enough, because a very common mistake, an anonymous object built in a loop, uses an external variable with the final value of the Loop!!!

is also the cause of the scope chain, the anonymous object uses an external variable, not the value of the variable, but extends the life cycle of the external variable, not destroyed at the time of the destruction (so easy to form a memory leak), so the anonymous object is called, the value of the external variable, is dependent on the value of the variable at this point (typically the final value of the loop execution, since the loop ends with an anonymous function call).

So, printing the value of the A.B will be 2.

So, finally, in the form of a new function, Observer.js is as follows:

Observer.prototype.transform = function(data){     for(varKeyinchData) { This. definereactive (Data,key,data[key]); }};observer.prototype.definereactive = function(data,key,value){    varDEP =NewDep (); Object.defineproperty (data,key,{Enumerable:true, Configurable:false,Get: function(){Console.log ("Intercept get:"+key);if(Dep.target) {//js's browser single-threaded feature guarantees that this global variable will only be used by the same listener at the same timeDep.addsub (Dep.target); }returnValue },Set: function(newval){Console.log ("Intercept set:"+key);if(newval = = value) {return; }//Using closure characteristics, changes can also be modified when value,get values are changed            //cannot use Data[key]=newval            //Because the set assignment continues to be called in set, causing a recursive callvalue = newval;//Monitor new valuesObserver (newval);        Dep.notify (newval); }    });//recursive processingObserver (value);};
2. Listening queue

Now we can intercept the object's getter/setter, that is, the assignment and value of the object we will know, we need to notify all listening to this object watcher, the data has changed, need to update the DOM, and so on, so we need to maintain a listening queue, All watcher who are interested in this object are registered in and receive notifications. This part has seen the implementation of Vue before, the feeling will not have a more ingenious way of implementation, so speak directly to the implementation of the principle.

    1. First, we intercept the getter;
    2. We want to add Wacher listener tmpwatcher for A.D;
    3. Assigns a global variable to a value target=tmpwatcher;
    4. Take the value A.D, also call to the A.D getter;
    5. In A.D getter, Target is added to the listening queue;
    6. target = null;

is so simple, as to why this can be done, because JS in the browser is single-threaded execution!! As a result, there is no other listener to modify the global variables when executing the listener's add process target!! so is this a local adaptation? 0_0

Detailed code can go to see the source code in GitHub implementation, in the Observer.js. Of course, he also has a more complex dependency, tick and other logic, I am just a simple implementation of one.

varDEP = function(){     This. Subs = {};};D Ep.prototype.addSub = function(target){    if(! This. Subs[target.uid]) {//Prevent repeated additions         This. subs[target.uid] = target; }};D ep.prototype.notify = function(newval){     for(varUidinch  This. Subs) { This. Subs[uid].update (newval); }};D Ep.target =NULL;
Three. Implement compiler

Here, is to see the source of DMQ, their own implementation of a code, because JS is not familiar with, made some small mistakes. Sure enough, the best way to learn the language is to write ~_~, then, the understanding of JS deepened a lot.
And because you want to achieve a bit more, that is, not just the simple variable placeholder such as {{A}}, but the expression, such as {{A+MATH.PI+B+FN (a)}, can not think of a good way, and go through the Vue source implementation, found that the implementation of Vue is not really elegant, But there is no better way to do it. Sometimes, having to write this code, such as enumerating all branches, is the simplest, most straightforward, and often the best approach.

1. The simplest implementation

That is, the pure variable placeholder, which everyone wants, with the regular analysis placeholder, the variable is added to listen, and the previous established setter/getter to establish a relationship.

2. Advanced Implementation--vue

Say the way Vue is implemented:

Principle:
    • Change the expression {{A+MATH.PI+B+FN (a)}} to a function:
function getter(scope) {    return  Math.PI + scope.b + scope.fn(scope.a);}
    • Called when the Vue Object Getter (VM) is passed in, so that variables, functions in all expressions, become calls within the scope of the VM.
The implementation of Vue

var body = exp.replace(saveRE, save).replace(wsRE, ‘‘);
* Use a few regular, first of all the strings are extracted, to replace, because all the space behind to remove;
* Remove spaces;
body = (‘ ‘ + body).replace(identRE, rewrite).replace(restoreRE, restore);
* Add all variables before the scope (except reserved words such as Math,date,isnan, etc., see the code in the regular);
* Replace all strings back
* Generate the above mentioned functions

You can see that this is a little bit time-consuming, so Vue did some optimizations and added a cache.

3. Problems encountered in the implementation
    • Understand a concept, each block of text in the DOM is also a node: The text node, and as long as the other nodes are separated, is a different text node;
    • JS, childnodes and attributes can be used to enumerate child nodes and attribute lists, etc.
    • [].foreach.call, which can be used to traverse non-array objects such as childnodes;
    • [].slice generates a shallow copy of the array, because ChildNodes changes the DOM object in real time, so the DOM cannot be modified directly in the traversal, and a shallow copy array can be generated to traverse;

The specific code is too long to show, you can directly look at Git source code.

Iv. realization of Watcher

There are several issues to be considered in the implementation of watcher:

    • The passed-in expression, such as the previous {{A+MATH.PI+B+FN (a)}}, establishes a relationship with each specific object and adds a listener;
    • How the added relationships are maintained, including:
      • The previous layer of the object is directly assigned, such as the expression is {{A.B.C}}, the assignment A.b={c:4}, at this time, C's getter is not triggered, and C-related watcher how to be notified;
      • Or the above example, how the newly added C establishes a relationship with the watcher of the old C;

In fact, the above is said to listen to the queue, has been slightly mentioned, the use of JS single-threaded features, before calling the object getter, dep.target this global variable is modified to watcher, and then getter added to the listener queue. So, in Watcher, you only need to take the value of the expression once, this function will be implemented, and watcher at initialization, it is necessary to call a value to initialize dom!

Take a look at the above questions:

    • First, watcher need to listen to an expression, all members of the expression need to listen, such as {{A+MATH.PI+B+FN (a)}} need to listen to A and B changes, and take this expression value, will call A and B getter, This will add itself to the listening queue for A and B!
    • About the maintenance of post-add relationships:
      • When we take the expression value {{A.B.C}}, the getter of A and B and C is called, which will add watcher to its own listening queue, so watcher will be triggered when A.b={c:4} is assigned!
      • When the above watcher is triggered, the A.B.C value is retrieved, and the new C getter is called, so that the new C adds watcher to its own listening queue.

Can be found that the above problems are satisfactorily resolved, if this is my own plan, I will be moved to cry t_t This is the elegant solution!

V. Realization of VUE

This is a public entrance and the entire framework is created from here. The goals to be achieved:

    • The serial connection of the process, observe object, compile Dom;
    • For their own object data, function methods and so on proxy, so can be directly used vm.a,vm.init and other calls, the same through Object.defineproperty object definition;

Concrete implementation is relatively simple, you can directly refer to the source code.

Vi. Summary

On the basis of Vue and DMQ, the realization of their own Vue simple implementation, the middle encountered a lot of problems, deepened the understanding of the JS language, but also a little touch with the Popular Front-end framework Vue architecture implementation, interested can see more of the source.

Understand the Vue implementation principle and implement a simple Vue framework

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.