• JS实现MVVM


    <!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" value="" v-model="user.name"/>
            {{user.name}}哈哈{{user.sex}}
            <select v-model="selected">
                <option value="">请选择学校</option>
                <option value="a">学校A</option>
                <option value="b">学校B</option>
            </select>
            <div>
                <div style="display: inline-block;">性别</div>
                <div>
                    <label for="man">男:</label>
                    <input id="man" type="radio" v-model="user.sex" value="man" name="sex">
                    <label for="woman">女:</label>
                    <input id="woman" type="radio" v-model="user.sex" value="woman" name="sex">
                </div>
            </div>
        </div>
        <script src="vm.js"></script>
        <script>
            let vm = new Vue({
                el:'#app'
                ,data:{
                    user:{
                        name:'杨文宇'
                        ,sex:'man'
                    }
                    ,selected:'a'
                }
            })
        </script>
    </body>
    </html>
    class Vue{
        constructor(options){
            this.$el = Vue.utils.getEl(options.el);
            this.$data = options.data;
            new Observe(this,this.$data);
            new Compile(this,this.$el);
        }
    
        static utils = {
            getEl(selector){
                return selector.nodeType == Node.ELEMENT_NODE ? selector : document.querySelector(selector)
            }
            ,isDirect(attrName){
                return /^v-/.test(attrName)
            }
            ,isTxtTpl(node){
                return node.nodeType == Node.TEXT_NODE && /^s*{{.*}}s*$/.test(node.textContent)
            }
            ,getVal(data,expOrFn){
                return expOrFn.split(".").reduce((data,cur)=>{
                    return data[cur];
                },data);
            }
            ,setVal(data,expOrFn,value){
                expOrFn.split(".").reduce((data,cur,index,arr)=>{
                    if(index == arr.length-1){
                        return data[cur] = value;
                    }
                    return data[cur];
                },data);
            }
        }
    }
    
    //劫持类
    class Observe{
        constructor(vm,data){
            this.observer(data);
            //创建代理
            this.dataProxy(vm,data);
        }
        //对数据进行劫持
        observer(data){
            Object.keys(data).forEach(key=>{
                this.reactive(data,key,data[key])
            })
        }
        //响应式函数
        reactive(obj,key,value){
            typeof value == 'object' && this.observer(value)
            let _self = this
            //为每一个属性创建依赖容器,因为有可能一个属性会被多个地方依赖
            , dep = new Dep();
            Object.defineProperty(obj,key,{
                get(){
                    //添加订阅者
                    dep.depend();
                    return value;
                }
                ,set(newVal){
                    if(value == newVal){return}
                    typeof newVal == 'object' && _self.observer(newVal);
                    value = newVal;
                    dep.notify();
                }
            })
        }
    
        dataProxy(obj,data){
            Object.keys(data).forEach(key=>{
                //let value = data[key];
                typeof value == 'object' && this.dataProxy(obj,value)
                Object.defineProperty(obj,key,{
                    get(){
                        return data[key]
                    }
                    ,set(newVal){
                        data[key] = newVal;
                    }
                })
            })
        }
    }
    
    class Compile{
    
        constructor(vm,el){
            this.el = el;
            this.vm = vm;
            this.ready();
        }
    
        static tagAttrEvent = {
            text:['value','input']
            ,textarea:['value','input']
            ,checkbox:['checked','change']
            ,radio:['checked','change']
            ,'select-one':['value','change']
            ,'select-multiple':['value','change']
        }
    
        //指令集
        static directSet = {
            model(vm,node,expOrFn){
                let tag = Compile.tagAttrEvent[node.type];
                new Watcher(vm,expOrFn,newVal=>{
                    node[tag[0]] = newVal;
                });
                node[tag[0]] = Vue.utils.getVal(vm.$data,expOrFn);
                node.addEventListener(tag[1],e=>{
                    Vue.utils.setVal(vm.$data,expOrFn,e.target[tag[0]])
                })
            }
            ,text(vm,node,expOrFn){
                let content = expOrFn.replace(/{{(.+?)}}/g,(...arg)=>{
                    new Watcher(vm,arg[1],newVal=>{
                        node.textContent = this.getContentVal(vm,expOrFn);
                    })
                    return Vue.utils.getVal(vm.$data,arg[1])
                })
                node.textContent = content;
            }
            ,getContentVal(vm,expOrFn){
                return expOrFn.replace(/{{(.+?)}}/g,(...arg)=>{
                    let value = Vue.utils.getVal(vm.$data,arg[1]) 
                    return typeof value == 'object'? JSON.stringify(value) : value;
                })
            }
        }
    
        ready(){
            let fragment = this.node2fragment(this.el)
            this.compiler(fragment);
            this.el.appendChild(fragment);
        }
    
        //编译
        compiler(mountEl){
            let childNodes = [...mountEl.childNodes];
            childNodes.forEach(node=>{
                //获取不是文本节点或不是空文本的节点
                if(node.nodeType == Node.ELEMENT_NODE){
                    [...node.attributes].forEach(attr=>{
                        let {name,value:expOrFn} = attr;
                        if(!Vue.utils.isDirect(name)){return}
                        Compile.directSet[name.split('-')[1]](this.vm,node,expOrFn)
                    })
                }
                if(Vue.utils.isTxtTpl(node)){
                    Compile.directSet['text'](this.vm,node,node.textContent)
                }
                node.childNodes.length > 0 && this.compiler(node)
            })
        }
    
        //将节点放到文档碎片流中
        node2fragment(el){
            let fragment = new DocumentFragment()
            , child;
            while(child = el.firstChild){
                fragment.appendChild(child)
            }
            return fragment;
        }
    }
    
    
    /**
     * 依赖收集--->收集的就是订阅对象
     */
    class Dep{
        
        static target = null;
    
        constructor(){
            this.subs = [];
        }
        //添加订阅者
        //谁用到这个数据,谁就是订阅者,反映在html中就是使用数据的这个dom元素
        addSub(watcher){
            this.subs.push(watcher)
        }
    
        depend(){
            Dep.target && this.subs.indexOf(Dep.target)<=-1 && this.addSub(Dep.target)
        }
    
        //通知所有订阅者
        notify(){
            this.subs.forEach(sub=>{
                sub.update();
            })
        }
    }
    
    /**
     * 谁用到这个数据,谁依赖这个数据,谁就是订阅者
     * 订阅者-->更新视图
     */
    class Watcher{
        constructor(vm,expOrFn,cb){
            this.vm = vm;
            this.expOrFn = expOrFn;
            this.cb = cb;
            this.oldVal = this.get()
        }
        get(){
            Dep.target = this;
            let value = Vue.utils.getVal(this.vm.$data,this.expOrFn);
            Dep.target = null;
            return value;
        }
        //更新视图
        update(){
            let value = Vue.utils.getVal(this.vm.$data,this.expOrFn);
            if(this.oldVal != value){
                this.cb(value);
                this.oldVal = value
            }
        }
    }
  • 相关阅读:
    .net log4dll的使用
    Myslq 5.7安装
    接口和抽象类有什么区别
    monkey测试
    JDK、Jmeter、Android环境变量配置
    聊天室
    tushrea知识笔记
    爬取图片
    python gui之tkinter事件处理
    ttk.Treeview
  • 原文地址:https://www.cnblogs.com/littleboyck/p/13744521.html
Copyright © 2020-2023  润新知