手写简化版vue2

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>

 

 

解释一下该图片:

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方法来更新视图              

   

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值