vue2 响应式源码

43 阅读1分钟
  • vue构造函数,初始化
export function Vue(options = {}){
    this.__init(options);
}

Vue.prototype.__init = function(options) {
   this.$el = options.el; // document.querySelector(#app); dom
   this.$options = options;
   this.$data = options.data;
   this.$methods = options.methods;
   // this.$data.message -> this.message, 将this.$data 代理到this上;
   proxy(this, this.$data);

   // observer 设置data属性响应式;配合Dep收集依赖和触发依赖;
   observer(this.$data);

   // complile
   new Compiler(this);
   
}

function proxy(target, data) {
    Object.keys(data).forEach(key => {
        Object.defineProperty(target, key, {
            configurable:true,
            enumerable:true,
            get(){
                return data[key];
            },
            set(newVal){
                if(newVal !== null && newVal !== data[key]){
                    data[key] = newVal;
                }
            }
        })
    })
}

function observer(data) {
    new Observer(data);
}
// 递归遍历属性设置响应式;配合Dep收集依赖和触发依赖;
class Observer {
    constructor(data){
        this.walk(data);
    }
    walk(data){
        if(typeof data !== "object"){
            return data;
        }
        if(data && typeof data === "object") {
            Object.keys(data).forEach(key => {
                this.defineReactives(data, key, data[key]);
            })
        }
    }
    defineReactives(target, key, value){
        let that = this; 
        // 递归遍历子属性;
        this.walk(value);
        let dep = new Dep();
        Object.defineProperty(target, key, {
            enumerable:true, // ke bianli
            configurable:true, // ke set
            get(){
                // collect dependence;
                if(Dep.target){
                    dep.add(Dep.target);
                }
                return value;
            },
            set(newVal){
                // 
                if(newVal !== null && newVal !== value){
                    value = newVal;
                }
                // 递归遍历子属性;
                that.walk(value);
                // 触发依赖;
                dep.notify();
            }
        })
    }
}

// 依赖 类;
class Dep {
    constructor(){
        this.watchers = new Set();
    }
    add(watcher){
        watcher && watcher.update && this.watchers.add(watcher);
    }
    notify(){
        this.watchers.forEach(watch => watch.update());
    }
}

// 观察者 类;
class Watcher {
    constructor(vm, key, cb){
        this.vm = vm;
        this.key = key;
        this.cb = cb;
        Dep.target = this;
        this.__old = vm[key]; // 触发get方法;收集依赖;
        Dep.target = null;
    }
    update(){
        let newVal = this.vm[this.key];
        if(newVal !== this.__old){
            this.cb(newVal); // 更新页面;
        }
    }
}

// 模板编译;<template> ->  render() -> vdom;
class Compiler {
    constructor(vm){
        this.vm = vm;
        this.el = vm.$el;
        this.methods = vm.$methods;
        this.compile(vm.$el);
    }
    compile(el){
        let childNodes = el.childNodes;
        Array.from(childNodes).forEach(node => {
            if(node.nodeType == 3){
                this.compileText(node);
            } else if(node.nodeType == 1){
                this.compileElement(node);
            }
            if(node.childNodes && node.childNodes.length){
                this.compile(node);
            }
        });
    }
    compileText(node){
        // 匹配插值表达式 {{ }}
        let reg = /\{\{(.+?)\}\}/;
        let textContent = node.textContent; // "{{ message }}""
        if(reg.test(textContent)){
            let key = RegExp.$1.trim();
            node.textContent = textContent.replace(reg, this.vm[key]); //  {{ message }} -> "hello luyi"
            new Watcher(this.vm, key, val => {
                // reflesh page; render function 
                node.textContent = val;
            })
        }
    }
    compileElement(node){
        if(node.attributes.length){
           Array.from(node.attributes).forEach(attr => {
                // v-on:click v-model
                let attrName = attr.name;
                if(attrName.startsWith("v-")){
                    attrName = attrName.indexOf(":") > -1 ? attrName.substr(5) : attrName.substr(2);
                }
                let key = attr.value; // v-on:click ["increase"]     v-model= ["message"]
                this.update(node, attrName, key, this.vm[key]);
           })
        }
    }
    update(node, attrName, key, value){
        if(attrName == "model"){
            node.value = value;
            node.addEventListener("input", () => {
                this.vm[key] =  node.value;
            })
            new Watcher(this.vm, key, val => {
                node.value = val;
            })
        } else if(attrName == "click"){
            node.addEventListener("click", this.methods[key].bind(this.vm));
        }
    }
}