欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

手写简化版vue2

程序员文章站 2024-03-22 23:57:40
...

vue.js

class Vue{
    constructor(options={}){
        this.$el = document.querySelector(options.el);
        let data = this.data = options.data;
        // 代理data,使this.data.xxx 变为this.xxx
        Object.keys(data).forEach(key=>{
            this.proxyData(key);
        })
        this.methdos = options.methods;//事件方法
        this.watcherTask = {};// 监听列表
        this.observer(data) // 劫持监听数据
        this.compile(this.$el); // 解析dom
        
    }
    proxyData(key){ // 单纯改变了 dta的访问形式
        let that = this;
        Object.defineProperty(that,key,{
            configurable: false, // 不可配置
            enumerable:true, // 可遍历
            get(){
                return that.data[key];
            },
            set(newVal){
                that.data[key]=newVal;
            }

        })

    }
    observer(data){ // 改变data的原始结构,监听数据
        let that = this;
        Object.keys(data).forEach(key=>{
            let value = data[key];
            this.watcherTask[key]=[];
            Object.defineProperty(data,key,{
                configurable:false,
                enumerable:true,
                get(){
                    return value;
                },
                set(newVal){
                    if(newVal!==value){ // 简单判断, 实际上会复杂很多
                        value = newVal;
                        //此处如果是深层对象 需要递归种植监听依赖dep
                        that.watcherTask[key].forEach(task=>{
                            task.update()
                        })
                    }
                }
            })
        })
    }
    compile(el){
        var nodes = el.childNodes;
        for(let i = 0;i<nodes.length; i++){
            const node = nodes[i];
            if(node.nodeType===3){ // 3 文本 
                var text = node.textContent.trim();
                if(!text)continue;
                this.compileText(node,'textContent')

            }else if(node.nodeType===1){ // 标签 
                 if(node.childNodes.length>0){
                     this.compile(node)
                 }
                 if(node.hasAttribute('v-model')
                 &&
                 (node.tagName==='INPUT'
                 ||
                 node.tagName === 'TEXTAREA')
                 ){
                
                    node.addEventListener('input',(()=>{
                        let attrVal = node.getAttribute('v-model');
                    
                        // 绑定监视器后 移除语法标记 
                        this.watcherTask[attrVal].push(
                            new Watcher(node,this,attrVal,'value')
                            )
                            node.removeAttribute('v-model');
                            return ()=>{
                                // 输入时改变 data的值
                                this.data[attrVal] = node.value;
                            }
                    })()) // 要调用一次 才能给输入控件赋值
                 }
                 if(node.hasAttribute('v-html')){
                     let attrVal = node.getAttribute('v-html');
                     this.watcherTask[attrVal].push(
                        new Watcher(node,this,attrVal,'innerHTML')
                        )
                        node.removeAttribute('v-html')
                 }
                 this.compileText(node,'innerHTML');
                 if(node.hasAttribute('@click')){
                    let attrVal = node.getAttribute('@click');
                    node.removeAttribute('@click');
                    node.addEventListener('click',e=>{
                    
                        // 把方法绑定到当前vm并且执行
                        this.methdos[attrVal] && this.methdos[attrVal].bind(this)();
                    })
                 }





            }
        }

    }
    compileText(node,type){
        let reg = /\{\{(.*?)\}\}/g,  txt = node.textContent;
        if(reg.test(txt)){
            node.textContent = txt.replace(reg,(matched,value)=>{
                value = value.trim();
                let tpl = this.watcherTask[value] || [];
                tpl.push(new Watcher(node,this,value,type));
                if(value.split('.').length>1){
                    let v=null;
                    // 根据属性拿到最终值
                    value.split('.').forEach((val,i)=>{
                        v = !v ? this[val] : v[val]
                    })
                    return v;
                }else{
                    return this[value]
                }
            })
        }

    }


}

class Watcher{
    constructor(el,vm,value,type){
        this.el= el;
        this.vm = vm;
        this.value = value;
        this.type = type;
        this.update();

    }
    update(){
        this.el[this.type] = this.vm.data[this.value]
    }
}

index.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='demo'> 
        <div><span>v-text:</span>{{name}}</div> 
        <div>v-html:<span v-html="name"></span></div>
     <div>
        <span>v-model:</span>
         <input v-model='name'/>
     </div>
     <div><button @click='nameClick'>变名字</button></div>
    </div>

    <script  src="./vue.js"></script>
    <script>
        new Vue({
            el:'#demo',
            data:{
                    name:'xiaoming',
                    age:25
                },
            methods:{
                    nameClick(){

                        this.name='朱钜明'
                    }
                }
        })

    </script>
</body>
</html>

 

手写简化版vue2

 

解释一下该图片:

1, 初始化

new vue  -> a, observer监听数据 

                     a1 会递归产生多个dep 依赖(有多少个属性就有多少个dep) ,subs 属性 用来存储 watcher  

               -> b,compile 开始编译模版 

                     b1 解析各类指令模版 ,

                     b2 在dom处安插watcher (wather的数量和页面的引用次数相等),同时 dep 和watch 双向绑定 

                     dep的subs 放置wathcer, 而watcher的id属性(id++)用来存放this.dep ,

                     b3, updater , 更新 页面  ,和watcher 建立发布订阅关系 

2, 数据发生改变需要更新

                此时 数据发生改变

               属性对应的dep的subs 循环遍历所有的watcher的update方法来更新视图