MVVM实现原理(三)—— 编译模板【Compile】

135 阅读2分钟

2.Compile

Compile主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
</head>

 
<body id="app">
    <div>
        a的值:{{a.a}}
    </div>
    <p>
       b的值 {{b}}
    </p>
</body>
<script src="./mvvm.js"></script>
<script>
    let vue = new Vue({
        el:'#app',
        data:{
            a:{
                a:"是a啊"
            },
            b:'是b啊'
        }
    })
    console.log(vue)
</script>
</html>

new Vue的时候,会调用  指令解析器,
指令解析器中的方法会把"#app"里面的内容递归处理成文档碎片,放到内存中去,
在内存中,递归文档碎片(虚拟节点),利用正则把“{{}}”替换成data中对应的数据【 此处只是简单实现,没有patch】
然后把内存中被处理后的文档碎片,放回页面,展示出来

function Vue(options={}){
    this.$options = options;//将所有属性挂载在options

    var data = this._data = this.$options.data;

    //观察者
    observe(data)

    //观察完之后,this代理了this._data  数据代理
    for(let key in data){
        Object.defineProperty(this,key,{
            enumerable:true,
            get(){
                return this._data[key];//获取到新值
            },
            set(newVal){
                this._data[key] = newVal//这里触发观察者【再次执行Observe(data)中的set方法】
            }
        })
    }
    
    //编译模板【指令解析器】
    new Compile(options.el,this)
}

function Compile(el,vm){
    //el代表替换的范围
    vm.$el = document.querySelector(el);

    /*获取到元素以后,把里面的子元素都拿到,需要移动到内存中操作,所以需要创建文档碎片*/
    //创建一个虚拟的节点对象,或者说,是用来创建文档碎片节点。它可以包含各种类型的节点,在创建之初是空的。
    let fragment = document.createDocumentFragment();

    //把app里面的内容都放到内存中,此时页面上为空白
    while(child  = vm.$el.firstChild){
        fragment.appendChild(child)
    }

    //递归 替换虚拟节点中的 {{}}
    replace(fragment)
    function replace(fragment){
        Array.from(fragment.childNodes).forEach((node)=>{
            //循环每一层
            let text = node.textContent;
            let reg = /\{\{(.*)\}\}/

            //nodeType为3时,是文本内容
            if(node.nodeType === 3 && reg.test(text)){
                //console.log(RegExp.$1) // a.a vm.b
                let arr = RegExp.$1.split('.') //[a,a]
                let val = vm;
                arr.forEach((k)=>{
                    val = val[k]
                })
                node.textContent = text.replace(/\{\{(.*)\}\}/,val)
            }
            if(node.childNodes){
                replace(node)
            }
        })
    }
    
    //把内存中的文档碎片放回到页面上,此时页面上的东西显示回来
    vm.$el.appendChild(fragment)

}



//vm.$options
function Observe(data){//这里写我们的主要逻辑
   for(let key in data){//把data属性通过object.defineProperty的方式定义

       let val = data[key]

       observe(val)//递归 使data的中的每个属性都被Object.defineProperty处理

       Object.defineProperty(data,key,{
           enumerable:true,
           get(){
               return val;
           },
           set(newVal){//更改值得时候
                if(newVal===val){//设置的值和以前是一样的东西
                    return;
                }
                val = newVal;//如果以后在获取值得时候,将刚才设置的值丢回去
                observe(newVal)
           }
       })
   }
}
function observe(data){
    if (typeof data !== 'object'){
        return 
    }
    return new Observe(data)
}