myvue 模擬vue核心原理

love發表於2020-04-18

// js部分index.js

 

class Myvue{
    constructor(options){
        this.data = options.data;
        this.dep = new Dep();
        var id = options.el;
        this.observe();
        var Dom = this.VnodeContainer(document.querySelector(id));
        document.querySelector(id).appendChild(Dom);
    }
    VnodeContainer(node,flag){ // 虛擬DOM容器
        var flag = flag || document.createDocumentFragment();
        var child;
        while(child = node.firstChild){
            this.compile(child);
            flag.appendChild(child);
            this.VnodeContainer(child,flag);
        }
        return flag;
    }
    compile(node){ // 編譯DOM
        let reg = /\{\{(.*)\}\}/g;
        if(node.nodeType === 1){ // 元素型別
            let attr = node.attributes;
            for(let i=0;i<attr.length;i++){
                if(attr[i].nodeName === 'v-model'){
                    let name = attr[i].nodeValue;
                    node.addEventListener('input',(e)=>{
                        this.data[name] = e.target.value;
                    });
                    node.value = this.data[name];

                    this.dep.add(new Watcher(this.data,node,name));
                }
            }
        }
        if(node.nodeType === 3){ // text型別節點
            if(reg.test(node.nodeValue)){
                let name = RegExp.$1; // 匹配到的字串
                name = name.trim();
                node.nodeValue = this.data[name];

                this.dep.add(new Watcher(this.data,node,name));
            }
        }
    }
    observe(){
        Object.keys(this.data).forEach((el)=>{
            this.definePropertyInit(this.data,el,this.data[el]);
        });
    }
    definePropertyInit(target,key,value){ // 將data做成響應式的
        
        Object.defineProperty(target,key,{
            get:()=>{
                return value;
            },
            set:(newVal)=>{
                if(newVal === value) return;
                value = newVal;
                this.dep.notify(); // 更新
            }
        });
    }
}
class Dep{ // 釋出者
    constructor(){
        this.subs = [];
    }
    add(sub){
        this.subs.push(sub);
    }
    notify(){
        this.subs.forEach((el)=>{
            el.update();
        });
    }
}
class Watcher{ // 觀察者(訂閱者)
    constructor(vm,node,name){
        Dep.global = this;
        this.vm = vm;
        this.node = node;
        this.name = name;
        this.update();
    }
    update(){
        this.get();
        switch (this.node.nodeType) {
            case 1: // 標籤元素
                this.node.value = this.value;
                break;
            case 3: // 文字
                this.node.nodeValue = this.value;
                break;
            default: break;
        }
    }
    get(){
        this.value = this.vm[this.name];
    }
}

let vm = new Myvue({
    el: '#app',
    data: {
        msg: 'hello'
    }
})

 

 

html部分

 

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>

<div id="app">
<input type="text" id="input" v-model='msg'>
<div id="box">{{msg}}</div>
</div>

<script src='./index.js'></script>
</body>
</html>

 

相關文章