There are already a lot of articles on the Internet, so take advantage of 3.0 to follow the trend

preface

Github pages is the most effective version of github/ vue-learning-source-code

Object.defineProperty

DefineProperty lets us hijack getters and setters for a property, for example:

var person = {
    firstName: 'meimei'.lastName: 'han'
};
Object.defineProperty(person, 'fullName', {
    get() {
        return this.lastName + ' ' + this.firstName;
    },
    set(val) {
        let arr = val.split(' ');
        this.lastName = arr[0];
        this.firstName = arr[1]; }});Copy the code

After hijacking the fullName, changing firstName or lastName updates the fullName and vice versa

The target

The goal of this article is to emulate the VUE implementation to update the DOM after changing the data so that the following code can work:

<div id="app">
    <p>firstName: {{firstName}}</p>
    <p>lastName: {{lastName}}</p>
    <p>fullName: {{fullName}}</p>
</div>
<script src="./vue.js"></script>
<script>
    let vm = new Vue({
        el: '#app',
        data() {
            return {
                firstName: 'meimei',
                lastName: 'han'
            };
        },
        computed: {
            fullName: {
                get: function(a) {
                    return this.lastName + ' ' + this.firstName;
                },
                set: function(val) {
                    let arr = val.split(' ');
                    this.lastName = arr[0];
                    this.firstName = arr[1]; }}}});</script>
Copy the code

Observer model

All we need to do is update the DOM when the data changes. The observer mode works well for the data. We only need to collect dependencies when the data changes.

class Dep {
    constructor() {
        this.subs=[]
    }
    addSub(item) {
        this.subs.push(item);
    }
    notify() {
        this.subs.forEach(item= >{ item.update(); }); }}Copy the code

Dom depends on data, so data get needs to be used in the process of obtaining DOM. Dependencies can be collected when data GET is performed. Data dependent data needs to be recorded when DOM update get data is performed when set data. Add a target attribute to class Dep as a logging tool and defineProperty as follows:

Dep.target = undefined;
function defineReactive(obj, key) {
    let dep = new Dep();
    let val = obj[key];
    Object.defineProperty(obj, key, {
        get: function() {
            if (Dep.target) {
                // Collect dependencies in get
                dep.addSub(Dep.target);
            }
            return val;
        },
        set: function(value) {
            val = value;
            // Set triggers an updatedep.notify(); }})}Copy the code

Now that the tools are in place, it’s time to iterate through data’s properties, using defineReactive

Data, computed, AND DOM dependencies

Parsing the DOM uses data and computed, computed GET uses data

  1. Through the data
function initData(vm) {
    let data = vm.$options.data;
    data = typeof data === 'function' ? data() : data;
    Object.keys(data).forEach(key= > {
        defineReactive(data, key);
    });
    // Proxies the attributes of data to the VM instance
    proxy(data, vm);
}
Copy the code
  1. Traverse the computed
function initComputed(vm) {
    let computed = vm.$options.computed;
    let defaultSetter = function(key) {
        console.error(this.' has no setter for ', key)
    }
    Object.keys(computed).forEach(key= > {
        let getter = typeof computed[key] === 'function' ? computed[key] : computed[key].get;
        let setter = typeof computed[key] === 'function' ? defaultSetter.bind(computed) : computed[key].set;
        Object.defineProperty(computed, key, {
            get: getter.bind(vm),
            set: setter.bind(vm)
        })
    })
    // Proxies computed attributes to vm instances
    proxy(computed, vm);
}
Copy the code
  1. Parsing the dom
function mount(vm) {
    let update = compile(vm);
    let watcher = new Watcher(update);
    // Set target to dom
    Dep.target = watcher;
    update();
    Dep.target = undefined;
}

function compile(vm) {
    let el = vm.$options.el;
    el = document.querySelector(el);
    vm.$el = el;
    let innerHTML = el.innerHTML;
    let getter = function() {
        return innerHTML.replace(/ {{(. *?) }}/g.function() {
            // This uses data computed GET to collect dependencies
            return vm[arguments[1]]}); };let update = function() {
        let iHTML = getter();
        el.innerHTML = iHTML;
    }
    return update;
}
Copy the code

Many say

When collecting dependencies, we add a target attribute to the Dep class, combined with targetStack in vUE. This collection approach can be buggy if managed carelessly, as mentioned in another article: Familiar with Vue? Can you explain this endless cycle? . Cheer for their own filling hole ~