vue双向绑定原理及代码解析

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <h3>测试</h3>
        <h3>{
   
   {msg}}</h3>
        <input type="text" v-model="msg">
        <ul>
            <li>1</li>
            <li>2</li>
            <li>3</li>
        </ul>
        <h3 v-text="msg"></h3>
        <h3 v-text="persion.name"></h3>
        <h3>123</h3>
        <h3>{
   
   {persion.name}}</h3>
        <h3 v-html="msg"></h3>
        <button @click="changeName">改变姓名</button>
    </div>
    <script src="./index.js"></script>
    <script>
        let vm = new MVue({
            el: '#app',
            data: {
                persion: {
                    name: '张三',
                    age: 18
                },
                msg: '测试'
            },
            methods: {
                changeName() {
                    this.$data.persion.name = '李四'
                    this.$data.msg = "说谎"
                }
            }
        })
    </script>
</body>

</html>
class MVue {
    constructor(options){
        this.$el = options.el;
        this.$data = options.data;
        this.$options = options;
        if(this.$el){
            //1.实现一个数据观察者
            new Observer(this.$data)
            //2.实现一个指令解析器
            new Compile(this.$el,this)
            //代理
            this.proxyData(this.$data)
        }
    }
    proxyData(data){
        for(const key in data){
            Object.defineProperties(this,key,{
                get(){
                    return data[key]
                },
                set(newVal){
                    data[key] = newVal
                }   
            })
        }
    }
}
class Compile {
    constructor(el,vm){
        this.el = this.isElementNode(el)? el:document.querySelector(el);
        this.vm = vm
        //1.获取文档碎片对象 放入内存中会减少页面的回流和重绘
        const fragment = this.node2Fragment(this.el)
        //2. 编译模板
        this.compile(fragment)
        //3.追加子元素到根元素
        this.el.appendChild(fragment)
    }
    compile(fragment){
        //1.获取子节点
        const childNode = fragment.childNodes;
        childNode.forEach(child=>{
            if(this.isElementNode(child)){
                //元素节点
                this.compileElement(child)
            }else{
                this.compileText(child)
                //文本节点
            }
            if(child.childNodes&&child.childNodes.length){
                this.compile(child)
            }
        })
    }
    compileElement(node){
        const attributes = node.attributes;
        if(attributes&&attributes.length){
            [...attributes].forEach(attr=>{
                const { name, value} = attr;
                if(this.isDirective(name)){
                    const [,dirctive] = name.split('-');
                    const [dirName,eventName] = dirctive.split(':');
                    //数据更新视图
                    compileUtil[dirName](node,value,this.vm)
                    // 删除标签上的属性
                    node.removeAttribute('v-'+ dirctive)
                }else if(this.isEventName(name)){
                    let [,eventName] = name.split('@');
                    compileUtil['on'](node,value,this.vm,eventName)
                }
            })
        }
    }
    isEventName(attrName){
        return attrName.startsWith('@')
    }
    isDirective(attrName){
        return attrName.startsWith('v-')
    }
    compileText(node){
        const content = node.textContent
        if(/\{\{(.+?)\}\}/.test(content)){
            compileUtil['text'](node,content,this.vm)
        }
    }
    node2Fragment(el){
        //创建文档碎片
        const f = document.createDocumentFragment()
        let firstChild;
        while(firstChild = el.firstChild){
            f.appendChild(firstChild)
        }
        return f
    }
    isElementNode(node){
        return node.nodeType===1;
    }
}
const compileUtil = {
    getVal(expr,vm){
        return expr.split('.').reduce((data,currentVal)=>{
            return data[currentVal]
        },vm.$data)
    },
    setVal(expr,vm,inputVal){
        return expr.split('.').reduce((data,currentVal)=>{
            data[currentVal] = inputVal
        },vm.$data)
    },
    getContentVal(expr,vm){
       return expr.replace(/\{\{(.+?)\}\}/,(...args)=>{
            return this.getVal(args[1],vm)
        })
    },
    text(node,expr,vm){
        let value;
        if(expr.indexOf('{
   
   {')!==-1){
            expr.replace(/\{\{(.+?)\}\}/,(...args)=>{
                new Watcher(vm,...args[1],(newVal)=>{
                    this.updater.htmlUpdater(node,this.getContentVal(expr,vm))
                })
                return this.getVal(args[1],vm)
            })
        }else{
            value = this.getVal(expr,vm)
        }
        this.updater.textUpdater(node,value)
    },
    html(node,expr,vm){
        const value = this.getVal(expr,vm)
        new Watcher(vm,expr,(newVal)=>{
            this.updater.htmlUpdater(node,newVal)
        })
        this.updater.htmlUpdater(node,value)
    },
    model(node,expr,vm){
        const value = this.getVal(expr,vm)
        //数据=》视图
        new Watcher(vm,expr,(newVal)=>{
            this.updater.modelUpdater(node,newVal)
        })
        // 视图=》数据=》视图
        node.addEventListener('input',(e)=>{
            this.setVal(expr,vm,e.target.value)
        })
        this.updater.modelUpdater(node,value)
    },
    on(node,expr,vm,eventName){
        let fn = vm.$options.methods&&vm.$options.methods[expr]
        node.addEventListener(eventName,fn.bind(vm),false)

    },
    updater:{
        textUpdater(node,value){
            node.textContent = value
        },
        htmlUpdater(node,value){
            node.innerHTML = value
        },
        modelUpdater(node,value){
            node.value = value
        }
    }
}
class Observer{
    constructor(data){
        this.observer(data)
    };
    observer(data){
        if(data&&typeof data === 'object'){
            Object.keys(data).forEach(key=>{
                this.defineReactive(data,key,data[key])
            })
        }
    };
    defineReactive(obj,key,value){
        const dep = new Dep()
        this.observer(value)
        Object.defineProperty(obj,key,{
            enumerable:true,
            configurable:false,
            get(){
                //订阅数据 往dep中添加观察者
                Dep.target && dep.addSub(Dep.target)
                return value
            },
            set:(newVal)=>{
                this.observer(value)
                if(newVal!=value){
                    value = newVal
                }
                dep.notify()
            }
        })
    }
}
class Dep{
    constructor(){
        this.subs = []
    }
    //收集观察者
    addSub(watcher){
        this.subs.push(watcher)
    }
    //通知观察者去更新
    notify(){
        console.log(98)
        this.subs.forEach(w=>w.update())
    }
}
class Watcher{
    constructor(vm,expr,cb){
        this.vm = vm;
        this.expr = expr;
        this.cb= cb;
        //先把旧值保存下来
        this.oldVal = this.getOldValue()
    }
    //判断是否有变化 更新视图
    update(){
        const newVal = compileUtil.getVal(this.expr,this.vm)
        if(newVal != this.oldVal){
            this.cb(newVal)
        }
    }
    getOldValue(){
        Dep.target = this
        const oldVal = compileUtil.getVal(this.expr,this.vm)
        Dep.target = null
        return oldVal
    }
}

猜你喜欢

转载自blog.csdn.net/xy19950125/article/details/120080031
今日推荐