MVVM简易实现源码

贴上代码便于日后复习
文件如下:

  • index.html
  • MVVM.js
  • observer.js
  • compile.js
  • watcher.js

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="app">
        <input type="text" v-model="message.a">
        <div>
            <ul>
                <li></li>
            </ul>
        </div>
        {{message.a}}
    </div>
    <script src="./watcher.js"></script>
    <script src="./observer.js"></script>
    <script src="./compile.js"></script>
    <script src="./MVVM.js"></script>
    <script>
            let vm=new MVVM({
            el:"#app",
            data:{
                message:{
                    a:'222222'
                    }
                }
            })
    </script>
</body>
</html>

MVVM.js:

class MVVM{
    constructor(options){
        //先把可用的东西挂载到实例上
        this.$el=options.el;
        this.$data=options.data;
        //如果有要编译的模板(el)我就开始编译
        if(this.$el){
            //数据劫持。把对想的所有属性改成
            //get和set方法
            new Observer(this.$data);
            //用数据和元素进行编译
            new Compile(this.$el,this);
        }
    }
}

observer.js:

class Observer{
    constructor(data){
        this.observe(data)
    }
    observe(data){
        //要对这个data数据将原有的属性改成getset的形式
        if(!data||typeof data !== 'object'){
            return 
        }
        //要将数据一一劫持先获取到data的key和value
        Object.keys(data).forEach(key=>{
            //劫持
            this.defineReactive(data,key,data[key])
            this.observe(data[key])//深度递归劫持
        })
    }
    //定义响应式
    defineReactive(obj,key,value){
        let that=this
        let dep=new Dep()
        Object.defineProperty(obj,key,{
            enumerable:true,//可枚举
            configurable:true,
            get(){
                Dep.target&&dep.addSub(Dep.target)
                return value
            },
            set(newValue){//当给data属性设置值得时候,更改获取的属性的值
                if(newValue!=value){
                    that.observe(newValue)//如果是对象继续劫持
                    value=newValue
                    dep.notify()
                }
            }
        })
    }
}
class Dep{
    constructor(){
        //订阅的数组
        this.subs=[]
    }
    addSub(watcher){
        this.subs.push(watcher)
    }
    notify(){
        this.subs.forEach(watcher=>watcher.update())
    }
}

compile.js:

class Compile{
    constructor(el,vm){
        this.el=this.isElementNode(el)?el:document.querySelector(el);//#app document.
        this.vm=vm;
        if(this.el){
            //如果这个元素能取到,我们才开始编译
            //1.先把这些真实DOM移入到内存中fragment
            let fragment=this.node2fragment(this.el)
            //2.编译=> 提取想要的元素节点 v-model 和文本节点 {{}}
            this.compile(fragment)
            //把编译好的fragment再塞回到页面里去
            this.el.appendChild(fragment)
        }
    }
    //专门写一些辅助的方法:如判断他是不是指令
    //判断el是不是dom节点的方法
    isElementNode(node){
        return node.nodeType===1;
    }
    //判断是不是指令:v-
    isDirective(name){
        return name.includes('v-')
    }
    //专门放一些核心的方法:
    compileElement(node){
        //带v-model
        let attrs=node.attributes;//取出当前节点的属性
        Array.from(attrs).forEach(attr=>{
            //判断属性名是不是包含v-model
            let attrName=attr.name
            if(this.isDirective(attrName)){
                //取到对应的值放到节点中
                let expr=attr.value;
                // let type=attrName.slice(2)//方法一
                let [,type]=attrName.split('-')//方法二
                //node this.vm.$data expr
                //toto......
                CompileUtil[type](node,this.vm,expr)
            }
        })
    }
    compileText(node){
        //带{{sdsd}}
        let expr=node.textContent;//取文本中的内容
        let reg=/\{\{([^}]+)\}\}/g
        //  首先 reg=/\{\{\}\}/g  所有大括号要转义 /g为全局
        //  接着 reg=/\{\{([^}]+)\}\}/g
        if(reg.test(expr)){
            //node this.vm.$data text 
            //todo......
            CompileUtil['text'](node,this.vm,expr)
        }
    }
    compile(fragment){
        //需要递归
        let childNodes=fragment.childNodes
        Array.from(childNodes).forEach(node=>{
            if(this.isElementNode(node)){
                //isElementNode方法在上面定义过
                //是元素节点,则需要继续深入检查
                //这里需要编译元素
                this.compileElement(node);
                this.compile(node)
            }else{
                //这里需要编译文本
                this.compileText(node)
            }
        })
    }
    node2fragment(el){//需要将el中的内容全放到内存中
        //文档碎片
        let fragment=document.createDocumentFragment();
        let firstChild;
        while(firstChild = el.firstChild){
            fragment.appendChild(firstChild)
        }
        return fragment
    }
}
CompileUtil={//获取实例上对应的数据
    getVal(vm,expr){
        expr=expr.split('.')//[a,v,c,s,a,w,r]
        return expr.reduce((prev,next)=>{//vm.$date.a
            return prev[next]
        },vm.$data)
    },
    getTextVal(vm,expr){//获取编译文本后的结果
        return expr.replace(/\{\{([^}]+)\}\}/g,(...arguments)=>{
            return this.getVal(vm,arguments[1])
        })
    },
    text(node,vm,expr){//文本处理
        let updateFn=this.updater['textUpdater']
        let value=this.getTextVal(vm,expr)
        expr.replace(/\{\{([^}]+)\}\}/g,(...arguments)=>{
            new Watcher(vm,arguments[1],(newValue)=>{
                updateFn&&updateFn(node,this.getTextVal(vm,expr))
            })
        })
        updateFn&&updateFn(node,value)
    },
    setVal(vm,expr,value){
        expr=expr.split('.');
        return expr.reduce((prev,next,currentIndex)=>{
            if(currentIndex === expr.length-1){
            return prev[next]=value;
            }
            return prev[next];
        },vm.$data)
    },
    model(node,vm,expr){//输入框处理
        let updateFn=this.updater['modelUpdater']
        new Watcher(vm,expr,(newValue)=>{
            updateFn&&updateFn(node,this.getVal(vm,expr))
        })
	node.addEventListener('input',(e)=>{
	    let newValue=e.target.value;
        this.setVal(vm,expr,newValue)
	})
        updateFn&&updateFn(node,this.getVal(vm,expr))
    },
    updater:{
        //文本更新
        textUpdater(node,value){
            node.textContent=value
        },
        //输入框更新
        modelUpdater(node,value){
            node.value=value
        }
    }
}

watcher.js:

//观察者的目的就是给需要变化的dom元素增加一个观察者,
//当数据变化后执行对应的方法
class Watcher{
    constructor(vm,expr,cb){
        this.vm=vm
        this.expr=expr
        this.cb=cb
        //先获取一下老的值
        this.value=this.get()
    }
    getVal(vm,expr){
        expr=expr.split('.')//[a,v,c,s,a,w,r]
        return expr.reduce((prev,next)=>{//vm.$date.a
            return prev[next]
        },vm.$data)
    }
    get(){
        Dep.target=this;
        let value=this.getVal(this.vm,this.expr)
        Dep.target=null
        return value;
    }
    update(){
        let newValue=this.getVal(this.vm,this.expr)
        let oldValue=this.value
        if(newValue!=oldValue){
            this.cb(newValue)//调用watch的callback
        }
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值