第二次剖析MVVM模式下的数据双向绑定及Vue数据的渲染及computed的实现

之前发不过一篇 Vue基于Object.defineProperty的双向绑定,今天又从新深入了解了一下

  • 首先通过Object.defineProperty来劫持数据,
  • 在编写的代码中封装了Observe函数来实现,可能数据有多层,所以会被多次调用,此函数主要是为了:观察对象,给对象增加Object.defindProperty
  • 封装了Compile模板编译函数,来对数据进行编译。创建了文档碎片:document.createDocumentFragment()
    createdocumentfragment()方法创建了一虚拟的节点对象,节点对象包含所有属性和方法。当你想提取文档的一部分,改变,增加,或删除某些内容及插入到文档末尾可以使用createDocumentFragment() 方法。
    你也可以使用文档的文档对象来执行这些变化,但要防止文件结构被破坏,createDocumentFragment() 方法可以更安全改变文档的结构及节点。
    在编译模板中通过正则 /\{\{(.*)\}\}/来查找{{}},根据文本节点,元素节点进行不同的编译模式,后面打算详细介绍一下,
  • 然后就是发布订阅模式,这里用了 Dep()Watcher(vm,exp,fn)实现
    -computed的实现主要是通过Object.keys()它可以将{name:1,age:2}=>[name,age] 将对象转化为数组
    具体的功能实现看具体的代码吧

js文件名为 mvvm.js,先进行导入

<!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">
       <p>{{a.a}}</p>
       <p>b的值是{{b}}</p>
       <div>
           <input type="text" v-mode='b'>
       </div>
       {{hello}}
    </div>
    <script src="./mvvm.js"></script>
    <script>
        let vue=new Vue({
            el:'#app',
            data:{a:{a:'是a'},b:'hhaha',c:'oK'} ,//Object.defineProperty
            //computed:  可以缓存,只是把数据挂载在了vm上
            computed: {
                hello(){
                    return this.b+this.c
                }
            },
        })
    </script>
</body>
</html>
function Vue(options={}){
    this.$options=options ; //将所有属性挂载在$options
    // this._data
    var data=this._data=this.$options.data;
    observe(data);
    //this 代理了 this._data
    for(let key in data){ //使得通过this可直接访问
        Object.defineProperty(this,key,{
            enumerable:true,
            get(){
                return this._data[key] ;//this.a={a:1}
            },
            set(newval){
                this._data[key]=newval
            }
        })
    }
    initComputed.call(this)
    new Compile(options.el,this)
}
//computed的实现
function  initComputed(){
    let vm=this;
   let computed= this.$options.computed; //Object.keys方法 {name:1,age:2}=>[name,age] 将对象转化为数组
    Object.keys(computed).forEach(function(key){
        Object.defineProperty(vm,key,{ //computed[key]
            get:typeof computed[key] === "function"?computed[key]:computed[key].get,
            set(){

            }
        })
    })
}
function Compile(el,vm){
    // el,表示替换的范围
    vm.$el=document.querySelector(el); 
     //创建文档碎片
    let fragment= document.createDocumentFragment();
    while(child=vm.$el.firstChild){
        //将app中的内容塞进内存之中
        fragment.appendChild(child);
    }
    replace(fragment)
    function replace(fragment){
        Array.from(fragment.childNodes).forEach(function(node){
            let text =node.textContent;
            let reg=/\{\{(.*)\}\}/;
            //如果是文本节点
            if(node.nodeType===3 && reg.test(text)){
                console.log(RegExp.$1); // a.a
                // console.log(RegExp.$1,'哈哈哈');
                let arr= RegExp.$1.split('.') //[a,a];
                let val=vm;
                arr.forEach(function(k){ //取this.a.a
                    val=val[k]
                });
                new Watcher(vm,RegExp.$1,function(newvla){  //函数里需要接受一个新的值
                    node.textContent=text.replace(/\{\{(.*)\}\}/,newvla)
                })
                node.textContent=text.replace(/\{\{(.*)\}\}/,val)
            }
            if(node.nodeType===1){
                //元素节点
                let nodeAttrs=node.attributes;//获取当前dom节点的属性
                Array.from(nodeAttrs).forEach(function(attr){
                    let name=attr.name; //type="text"
                    let exp=attr.value; //v-mode="b"
                    if(name.indexOf('v-')==0){ //v-model
                        node.value=vm[exp]
                    }
                    new Watcher(vm,exp,function(newVal){
                        node.value=newVal; //当watcher触发时会自动将内容放入到输入框内
                    });
                    node.addEventListener('input',function(e){
                        let newVal=e.target.value;
                        vm[exp]=newVal;

                    })
                })
            }
            if(node.childNodes){
                replace(node)
            }
        })
    }
    // 拿到每一个节点
    //转换为数组
   
    vm.$el.appendChild(fragment)
}
function Observe(data){ //这里写主要逻辑
    let dep=new Dep()
    for(let key in data){  //把data属性通过Object.definePropertyd的方式 定义属性
        let val=data[key];
        observe(val)
        Object.defineProperty(data,key,{
            enumerable:true, //可枚举
            get(){
                Dep.target&&dep.addSub(Dep.target) //[watcher]
                return val
            },
            set(newval){ //更改值得时候
                if(newval===val){ //设置的值和以前的一样是
                    return
                }
                val=newval; //如果以后再获取值得时候,将刚才设置的值再丢回去
                observe(newval)
                dep.notify(); //让所有的watch 的update方法执行即可
            }
        })
    }
}
//观察对象,给对象增加Object.defindProperty
function observe(data){
    if(typeof data !=="object")return
    return new Observe(data)
}
// Vue的特点不能新增不存在的属性  不存在的属性没有get和set
// 深度响应  因为每次赋予一个新对象时会给这个新对象增加数据劫持


//发布订阅
function Dep(){
    this.subs=[];

}
Dep.prototype.addSub=function(sub){ //订阅
    this.subs.push(sub)
}
Dep.prototype.notify=function(){ //通知
    this.subs.forEach(sub=>sub.update())
}

//vm,当前实例
function Watcher(vm,exp,fn){ //Watcher是一个类,通过这个类创建的实例都拥有update方法
    this.fn=fn;
    this.vm=vm;
    this.exp=exp; // 添加到订阅中
    Dep.target=this;
    let val=vm;
    let arr=exp.split('.');
    arr.forEach(function(k){ //this.a.a
        val=val[k]
    })
    Dep.target=null;
} 
Watcher.prototype.update=function(){
  
    let val=this.vm;
    let arr=this.exp.split('.');
    arr.forEach(function(k){
        val=val[k]
    })
    this.fn(val) //newval
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值