MVVM中双向绑定的探究

178 阅读2分钟

分析

  1. 劫持数据
  2. 指令解析
  3. 数据或View发生变化时更新

实践

数据劫持是由Object.defineProperty()来实现

var Observe = function(data){
    Object.keys(data).forEach(key=>{
        var dep = new Dep();
        Object.defineProperty(data,key,{
            get:function(){
                if(Dep.target){
                    // 将watcher和dep联系起来
                    Dep.target.addDep(dep)
                }
                return data[key];
            },
            set:function(newVal){
                // 对数据的赋值操作
                // 需要更新View
                // 由Dep进行桥接
                data[key] = newVal;
                dep.notify()
            }
        })
    })
}

当取值时var val = data.key时将触发getter
当赋值时data.key = val时,将触发setter
2. 指令解析就是对DOM自定义属性Mustache语法进行解析。同时我们还需要初始化View

<div id="app">
    {{msg}}
    <span v-text="msg"></span>
    <input type="text" v-model="msg" />
</div>

我们需要解析{{msg}}v-textv-model

var Compile = function(el,data){
    this._el = document.querySelector(el);
    this._data = data;
    this.compileDOM(this._el)
}
Compile.prototype = {
    compileDOM:function(el){
        // 匹配 Mustache
        var regExp = /\{\{(\w+)\}\}/;
        var childNodes = el.childNodes;
        Array.from(childNodes).forEach(node=>{
            if(node.nodeType === 1){
                this.parseAttrs(node);
            }else if(node.nodeType === 3 && regExp.test(node.textContent)){
                Parse.text(node,this._data,RegExp.$1)
            }
            
            if(node.childNodes && node.childNodes.length){
                this.compileDOM(node)
            }
        })
    },
    parseAttrs:function(node){
        var attrs = node.attributes;
        [].slice.call(attrs).forEach(attr=>{
            // 属性名
            var attrName = attr.name;
            if(attrName.startsWith('v-')){
                var path = attrName.substring(2);
                // 属性值 对应数据中的key值
                var attrVal = attr.value;
                Parse[path](node,this._data,attrVal)
            }
        })
    }
}

初始化View

var Parse = {
    text:function(node,data,key){
        // 这里进行了取值操作
        node.textContent = data[key];
        // 当数据发生变化 更新View
        new Watcher(data,key,function(newVal){
            node.textContent = newVal;
        })
    },
    model:function(node,data,key){
        // 这里进行了取值操作
        node.value = data[key];
        // 绑定监听函数
        node.addEventListener('input',function(){
            // 这里进行了赋值操作 View发生了变化
            data[key] = this.value;
        });
        // 当数据发生变化 更新View
        new Watcher(data,key,function(newVal){
            node.value = newVal;
        })
    }
}

至此解析指令并初始化View完成,然而当数据和View还没有任何联系,接下来就是让他们产生联系

var uid = 0;
var Dep = function(){
    this.id = uid++;
    this.subs = [];
}
Dep.target = null;
Dep.prototype = {
    addWatcher:function(watcher){
        // 绑定一个数据的watcher
        this.subs.push(watcher)
    },
    notify:function(){
        // 赋值操作会触发notify
        // 再由Dep触发绑定的Watchers更新
        this.subs.forEach(watcher=>{
            watcher.update();
        })
    }
}

Dep作为数据和Watcher的桥接,取值操作时绑定此数据的watcher,赋值操作时触发watcher的更新

var Watcher = function(data,key,cb){
    this._data = data;
    this._key = key;
    this._cb = cb;
    this.deps = {};
    // 获取数据
    // 将Watcher和Dep关联起来
    this.value = this.get()
}
Watcher.prototype = {
    get:function(){
        Dep.target = this;
        // 这里进行了取值操作
        var val = this._data[this._key];
        Dep.target = null;
        return val;
    },
    update:function(){
        var newVal = this.get();
        if(this.value !== newVal){
            this.value = newVal;
            this._cb(newVal);    
        }
    },
    addDep:function(dep){
        if(!this.deps[dep.id]){
            this.deps[dep.id] = dep;
            dep.addWatcher(this);
        }
    }
}

Watcher数据的View的桥梁,一条数据对应一个watcher
View发生变化,触发数据变化,watcher在更新View
最后就是整合了

var Mvvm = function(opts){
    this._el = opts.el;
    this._data = opts.data;
    Object.keys(this._data).forEach(key=>{
        Object.defineProperty(this,key,{
            get:function(){
                return this._data[key]
            },
            set:function(newVal){
                this._data[key] = newVal;
            }
        })
    })
    new Observer(this._data);
    new Compile(this._el,this)
}

参考文档 合格前端系列

PS:虽然如今大火,但是一直没有仔细研究过,粗略的看过后发现被问到并不能回答上来,只能自己写一遍!欢迎指正!