After reading so many articles, I found that many articles only tell you how he writes and don’t tell you what he thinks. And I think the most important thing about being able to write code is how is it conceived? Why do some people write elegant code while others write bloated code? Why are some people able to write consistently while others tend to “aborte”? I hope you found this article interesting. Thank you for reading!

Reverse thinking

I don’t know if you’ve ever tried to find the entry to the Vue source code, but of course it’s easy for the old hands who are familiar with code auditing. But if you don’t have any code audit experience, I think you’ll get a headache too. Of course, I’m not talking about code auditing here. I’m going to show you how to do something similar without reading the source code, which I call reverse thinking.

Of course when you want to imitate something you first have to be familiar with it, but also have a very clear thinking. Here’s how I implemented Vue data bidirectional binding in the simplest way:

<! -- html -->
<div id="app">{{name}}</div>
Copy the code
const app = new Vue({
    el: '#app'.data: {
        name: 'Fish Chan'}});Copy the code

This is the simplest Vue code that I believe anyone who has learned Vue can understand. The above code creates a Vue instance and passes a parameter (object type).

So I created a new file, core.js, which looks like this:

// Target: I need a Vue class. The constructor can take one parameter
class Vue {
    constructor(options) {
        // TODO compiles templates and implements bidirectional data binding}}Copy the code

So here we have a basic Vue class that doesn’t do anything. So let’s move on. The contents of the replacement template are _ compiled _, so I create another file called compile.js (mimics Java thinking, one file per class, so that each file is small and it is clear what each file does) :

{{name}}}
class Compile {
    constructor() {
        // TODO builds templates}}Copy the code

Again, I didn’t write anything substantive, because I always adhere to the principle of not writing useless code, write when I use it, so my code habit is to pass the data I need to use it.

Now my Compile needs to know where to start compiling, so we pass in the first parameter el; I also need to replace the template contents with real data, so I pass a second parameter, the vUE instance that carries the data:

class Compile {
    constructor(el, vue) {
        this.$el = document.querySelector(el);
        this.$vue = vue;
    }
    
    compileText() {
        // TODO compiles the template, finds {{name}} and replaces it with real data}}Copy the code

To get a little bit of traction, you’ll notice that I use TODO to write the next step in my code, which is not necessary when you’re thinking clearly, unless you need to leave your computer desk for an emergency.

Compiling templates

We continue to compile. Js along the way:

class Compile {
    constructor(el, vue) {
        this.$el = document.querySelector(el);
        this.$vue = vue;
    }
    
    compileText() {
        const reg = / \ {\ {(. *) \} \} /; // The re used to match {{name}}
        
        const fragment = this.node2Fragment(this.$el); // change operation DOM to operation document fragment
        const node = fragment.childNodes[0]; // fetch node _ object _
        
        if (reg.test(node.textContent)) {
            let matchedName = RegExp. $1;
            node.textContent = this.$vue._data[matchedName]; // Replace data
            this.$el.appendChild(node); // The compiled document fragment is put into the root node
        }
    }
    
    node2Fragment(node) {
        const fragment = document.createDocumentFragment();
        fragment.appendChild(node.firstChild);
        returnfragment; }}Copy the code

In fact, we have finished compiling the template at this point. Now we just need to call it from core.js:

class Vue {
    constructor(options) {
    	let data = this._data = options.data;
    	
    	const _complie = new Compile(options.el, this); _complie.compileText(); }}Copy the code

Let’s run it:

Two-way data binding

Well, compiled templates have been implemented, and now to implement two-way data binding, I want you to know about the observer and Object.defineProperty design patterns first.

Create a new Observer class for two-way data binding:

class Observer {
    constructor(data) {
        this.defineReactive(data);
    }
    
    defineReactive(data) {
        Object.keys(data).forEach(key= > {
            let val = data[key];
            Object.defineProperty(data, key, {
                get() {
                    // TODO listens to data
                    return val;
                },
                set(newVal) {
                    val = newVal;
                    // TODO updates the view}})}); }}Copy the code

Then there is the implementation of the Observer pattern, which is basically a fixed template (I think design patterns are very easy to learn, like mathematical formulas) :

class Dep {
    constructor(vue) {
        this.subs = []; // Store subscribers
    }
    
    addSubscribe(subscribe) {
        this.subs.push(subscribe);
    }
    
    notify() {
        let length = this.subs.length;
        while(length--)
        {
            this.subs[length].update(); }}}Copy the code

Next comes Watcher, the subscriber, and all the subscriber has to do is execute an event:

class Watcher {
    constructor(vue, exp, callback) {
        this.vue = vue;
        this.exp = exp;
        this.callback = callback;
        this.value = this.get();
    }
    
    get() {
        Dep.target = this;
        let value = this.vue._data[this.exp];
        Dep.target = null;
        return value;
    }
    
    update() {
        this.value = this.get();
        this.callback.call(this.vue, this.value); // Pass new data back to update the view; This is guaranteed to point to vue}}Copy the code

In this way, a two-way data binding is simply implemented by copying the observer pattern and using Object.defineProperty.

The complete code

By substituting all of the TODO parts, we have implemented all of the functionality:

index.html


      
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="author" content="Fish Chan">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>vue-demo</title>
    <script src="./Dep.js"></script>
    <script src="./Watch.js"></script>
    <script src="./Compile.js"></script>
    <script src="./Observer.js"></script>
    <script src="./core.js"></script>
</head>
<body>
    <div id="app">{{name}}</div>

    <script>
    const app = new Vue({
        el: '#app',

        data: {
            name: 'Fish Chan'}});</script>
</body>
</html>
Copy the code

core.js

class Vue {
    constructor(options) {
        let data = this._data = options.data;

        new Observer(data);

        const _complie = new Compile(options.el, this); _complie.compileText(); }}Copy the code

Observer.js

class Observer {
    constructor(data) {
        this.defineReactive(data);
    }
    
    defineReactive(data) {
        let dep = new Dep();
        Object.keys(data).forEach(key= > {
            let val = data[key];
            Object.defineProperty(data, key, {
                get() {
                    Dep.target && dep.addSubscribe(Dep.target);
                    returnval; }, set(newVal) { val = newVal; dep.notify(); }})}); }}Copy the code

Compile.js

class Compile {
    constructor(el, vue) {
        this.$el = document.querySelector(el);
        this.$vue = vue;
    }
    
    compileText() {
        const reg = / \ {\ {(. *) \} \} /; // The re used to match {{name}}
        
        const fragment = this.node2Fragment(this.$el); // change operation DOM to operation document fragment
        const node = fragment.childNodes[0];
        
        if (reg.test(node.textContent)) {
            let matchedName = RegExp. $1;
            node.textContent = this.$vue._data[matchedName]; // Replace data
            this.$el.appendChild(node); // The compiled document fragment is put into the root node

            new Watcher(this.$vue, matchedName, function(value) {
                node.textContent = value;
                console.log(node.textContent);
            });
        }
    }
    
    node2Fragment(node) {
        const fragment = document.createDocumentFragment();
        fragment.appendChild(node.firstChild);
        returnfragment; }}Copy the code

Watch.js

class Watcher {
    constructor(vue, exp, callback) {
        this.vue = vue;
        this.exp = exp;
        this.callback = callback;
        this.value = this.get();
    }
    
    get() {
        Dep.target = this;
        let value = this.vue._data[this.exp];
        Dep.target = null;
        return value;
    }
    
    update() {
        this.value = this.get();
        this.callback.call(this.vue, this.value); // New data is passed back to update the view}}Copy the code

Dep.js

class Dep {
    constructor(vue) {
        this.subs = []; // Store subscribers
    }
    
    addSubscribe(subscribe) {
        this.subs.push(subscribe);
    }
    
    notify() {
        let length = this.subs.length;
        while(length--)
        {
            this.subs[length].update(); }}}Copy the code

Take a look at the final runtime:

conclusion

In addition to having a solid foundation, you must think clearly when writing code. Clarity can mean the difference between writing elegant code and seeing a project through to completion.