Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue双向数据绑定实现 #18

Open
isNeilLin opened this issue Dec 16, 2017 · 0 comments
Open

Vue双向数据绑定实现 #18

isNeilLin opened this issue Dec 16, 2017 · 0 comments
Labels
框架&工具库 Vue和React等前端框架或工具库相关

Comments

@isNeilLin
Copy link
Owner

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <h2>{{title}}</h2>
        <h1>{{name}}</h1>
        <input type="text" v-model="name" v-on:blur="blur">
        <button v-on:click="clickMe">click me!</button>
    </div>
<script>
    // 监听器
    function observe(data){
        if(Object.prototype.toString.call(data)!=='[object Object]'){
            return;
        }
        Object.keys(data).forEach(key=>{
            defineReactive(data,key,data[key])
        })
    }
    function defineReactive(data,key,value){
        observe(value);
        var dep = new Dep();
        Object.defineProperty(data,key,{
            enumerable: true,
            configurable: true,
            get: function(){
                if(Dep.target){
                    dep.addSub(Dep.target); // 添加一个订阅者
                }
                return value;
            },
            set: function(newVal){
                if(newVal === value){
                    return;
                }
                console.log('value changed,\nthe old value is '+ value + ',\nthe new value is ' + newVal);
                value = newVal;
                dep.notify(); // 如果数据变化,通知所有订阅者
            }
        })
    }
    
    // 消息订阅器
    function Dep(){
        this.subs = [];
    }
    Dep.prototype = {
        constructor: Dep,
        addSub: function(sub){
            this.subs.push(sub);
        },
        notify: function(){
            this.subs.map(sub=>{
                sub.update();
            })
        }
    }

    // 订阅器
    function Watcher(vm,exp,cb){
        this.vm = vm;
        this.exp = exp;
        this.cb = cb;
        this.value = this.get();
    }
    Watcher.prototype = {
        constructor: Watcher,
        get: function(){
            Dep.target = this;
            var value = this.vm.data[this.exp] // 强制执行监听器里的get函数
            Dep.target = null;
            return value;
        },
        update: function(){
            this.run();
        },
        run: function(){
            var value = this.vm.data[this.exp];
            var oldVal = this.value;
            if(value!==oldVal){
                this.value = value;
                this.cb.call(this.vm,value,oldVal);
            }
        }
    }
    
    // 解析器
    /* 将需要解析的dom节点存入fragment片段 */
    function Compile(el,vm){
        this.vm = vm;
        console.log(this.vm);
        this.el = document.querySelector(el);
        this.fragment = null;
        this.init();
    }
    Compile.prototype = {
        constructor: Compile,
        init: function(){
            if(!this.el){
                return;
            }
            this.fragment = this.nodeToFragment(this.el);
            this.compileElement(this.fragment);
            this.el.appendChild(this.fragment);
        },
        nodeToFragment: function(el){
            var fragment = document.createDocumentFragment();
            var child = el.firstChild;
            while(child){
                fragment.appendChild(child);
                child = el.firstChild;
            }
            return fragment;
        },
        compileElement: function(el){
            var childNodes = el.childNodes;
            var self = this;
            [].slice.call(childNodes).forEach(child=>{
                var text = child.textContent;
                var reg = /\{\{(.*)\}\}/;
                if(self.isElementNode(child)){
                    self.compileDirective(child);
                }
                if(self.isTextNode(child) && reg.test(text)){
                    self.compileText(child, reg.exec(text)[1]);
                }
                if(child.childNodes && child.childNodes.length){
                    self.compileElement(child);
                }
            })
        },
        compileDirective: function(node){
            var attrs = node.attributes;
            var self = this;
            [].slice.call(attrs).forEach(attr=>{
                var name = attr.name;
                if(self.isDirective(name)){
                    var exp = attr.value;
                    var dir = name.substring(2);
                    if(self.isEventDirentive(dir)){ // 事件指令
                        console.log('event')
                        self.compileEvent(node,self.vm,exp,dir)
                    }else{ // v-model指令
                        self.compileModel(node,self.vm,exp,dir)
                    }
                    node.removeAttribute(name);
                }
            })
        },
        compileText: function(node,exp){
            var self = this;
            var initText = this.vm[exp];
            this.updateText(node,initText);
            new Watcher(this.vm,exp,function(value){
                self.updateText(node,value)
            })
        },
        compileEvent: function(node,vm,exp,dir){
            var eventName = dir.split(':')[1];
            var cb = this.vm.methods && this.vm.methods[exp];
            if(eventName && cb){
                console.log('bind event')
                node.addEventListener(eventName,cb.bind(vm),false);
            }
        },
        compileModel: function(node,vm,exp,dir){
            var self = this;
            var val = this.vm[exp];
            this.modelUpdater(node, val);
            new Watcher(this.vm,exp,function(value){
                self.modelUpdater(node,value);
            })
            node.addEventListener('input',function(e){
                var newValue = e.target.value;
                if(newValue===val){
                    return
                }
                self.vm[exp] = newValue;
                val = newValue;
            })
        },
        updateText: function(node,text){
            node.textContent = typeof text === 'undefined' ? '' : text;
        },
        modelUpdater: function(node,text){
            node.value = typeof text === 'undefined' ? '' : text;
        },
        isTextNode: function(node){
            return node.nodeType === 3;
        },
        isElementNode: function(node){
            return node.nodeType === 1;
        },  
        isDirective: function(directive){
            return directive.indexOf('v-')===0;
        },
        isEventDirentive: function(directive){
            return directive.indexOf('on:')===0;
        }
    }

    function SelfVue(options){
        var self = this;
        this.data = options.data;
        this.methods = options.methods;
        Object.keys(this.data).forEach(key=>{
            self.proxyKey(key);
        })
        observe(this.data);
        new Compile(options.el,this);
        options.mounted.call(this);
    }
    SelfVue.prototype.proxyKey = function(key){
        var self = this;
        Object.defineProperty(self,key,{
            get: function(){
                return self.data[key];
            },
            set: function(newVal){
                self.data[key] = newVal;
            }
        })
    }
    var selfVue = new SelfVue({
        el: '#app',
        data: {
            title: 'hello world',
            name: 'canfoo'
        },
        methods: {
            clickMe: function () {
                this.title = 'hello world';
            }
        },
        mounted: function () {
            window.setTimeout(() => {
                this.title = '你好';
            }, 1000);
        }
    })
</script>
</body>
</html>
@isNeilLin isNeilLin added the Vue label Dec 16, 2017
@isNeilLin isNeilLin added 框架&工具库 Vue和React等前端框架或工具库相关 and removed Vue labels Jun 18, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
框架&工具库 Vue和React等前端框架或工具库相关
Projects
None yet
Development

No branches or pull requests

1 participant