手写一个mvvm框架

253 阅读3分钟

理解mvvm的设计思想

如图所示,数据的变化通过数据劫持去通知界面上的dom可以变更,dom的变化又有一个监听器去监听到他然后通知数据model层去改变。形成一个闭环。

mvvm的三要素:数据响应式,模版引擎及渲染。

数据响应式:

  • vue中利用defineProperty进行数据拦截实现,在get中进行依赖手机,在set中进行变更通知。

    //object.defineproperty(obj,key,descriptor) function definReactive(obj,key,val){ observe(val)//递归子元素 Object.defineproperty(obj,key,{ get(){ console.log(get${key}) return val } set(){ console.log(set ${key}) observe(newVal)//新值如果是对象,对她执行响应式处理,比如obj.a={a:'haha'} val=newVal update()//通知dom更新 } }) } //这里形成了一个闭包,set之后的val保存在内存中,get的时候取出来。 //通知dom更新的方法 function update(){ app.innerHTML=obj.foo } //对对象的key进行批量处理响应式 function observe(obj){ if(typeof obj !=='object'|| obj==null){return obj} Object.keys(obj).forEach((key)=>{ defineReactive(obj,key,obj[key]) }) } //模拟set方法 function set(obj,key,val){ defineReactive(obj,key,val) } 数组响应式:数组有七个变更方法,复写这个变更方法。

范例html

<div id="app"></div>
<script>
const app=new Vue({
    el:'#app',
    data(){
        return {
            counter:1
        }
    }
})
</script>

实现原理:

  1. new vue() 首先执行初始化,对data进行响应化处理,这个过程发生在observe中
  2. 同时对模版进行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在compile中
  3. 同时定义一个更新函数和watcher,将来对应数据变化时watcher会更新函数。
  4. 由于data的某个key在视图中可能出现多次,所以每个key需要一个管家dep来管理多个watcher
  5. 将来data中的数据一旦发生变化,会首先找到对应的dep,通知所有的watcher执行更新函数。

涉及类型介绍:

  • kVue:框架的构造函数
  • observer:分辨数据时对象还是数组,执行数据的响应化。
  • compile:编译模版,初始化视图,收集依赖(更新函数,watcher创建)
  • watcher:执行更新函数(更新dom)
  • dep:管理多个dom,批量更新。

KVue

class KVue{
    constructor(options){
        this.options=options;
        this.$data=options.data
        //对data数据进行响应式处理
        observe(this.$data)
        //代理data到vm上
        proxy(this)
    }
}
class Observe{
    constructor(value){
        //数组
        if(Array.isArray(val)){
        }else{
            this.walk(val)
        }
    }
}
walk(obj){
    Object.keys(obj).forEach((key)=>{defineReactive(obj,key.obj[key])})
}
//为了将数据代理到this上,而不是this.$data[key]
function proxy(vm){
    Object.keys(vm.$data).forEach(key=>{
        Object.defineProperty(vm,key,{
            get(){
                return vm.$data[key]
            },
            set(){
                vm.$data[key]==v
            }
        })
    })
}

编译器compile

  • 处理插值表达式{{}}

  • 指令解析,绑定事件解析

    //遍历dom树,找到动态的表达式和指令 class Compile{ constructor(el,vm){ this.vm=vm;this.vm=vm; this.el=document.querySelector(el)if(this.el) if(this.el){ this.compile(this.$el) } } } //递归传入的节点,根据节点类型做不同的操作 function compile(el){ const childNodes=el.childNodes childNodes.forEach(node=>{ if(node.nodeType===1){ //console.log('元素节点',node.nodeName) this.compileElement(node) }else if(this.isInter(node)){ //console.log('文本节点') this.compileText(node)

        }
        if(node.hasChildNodes){
            this.compile(node.childNodes)
        }
    })
    

    } isInter(node){ //形如{{xx}} return node.nodeType===1&&/{{2}(.*)}{2}/.test(node.textContent) } //给插值表达式赋值{{xxx}} compileText(node){ node.textContent=this.vm[RegExp.vm[RegExp.1] } //处理指令v-text compileElement(node){ let nodeAttrs=node.attributes Array.from(nodeAttrs).forEach(attr=>{ //形如v-text='xxx' const attrName=attr.name; const exp=attr.value if(attrName.indexOf('k-')===0){ //指令 //每个指令有其特定的处理函数 const dir=attrName.substring(2) this.[dir]&&this.dir } })} //k-text text(node,exp){ node.textContent=this.vm[exp] } html(node,exp){ node.innerHTML=this.vm[exp] }

依赖收集

//负责更新视图
class Watcher{
    constructor(vm,key,updater){
        this.updaterFn=updater
        this.key=key
        this.vm=vm
        //创建实例时,把当前实例指定到dep.target静态属性上
        Dep.target=this
        //读一下key,触发get
        vm[key]
        //置空
        Dep.target=null
    }
   update(){
       this.updaterFn.call(this.vm,this.vm[this.key])
   }}
//调用watcher,重构刚才的text和html
//遇到绑定表达式或者指令
//初始值
//创建watcher实例管理当前node的更新
update(node,exp,dir){
    const fn=this[dir+'Updater']
    fn&&fn(node,this.$vm[exp])
    //更新函数
    new Watcher(this.$vm,exp,val=>{
        fn&&fn(node,this.$vm[exp])
    })
}
//k-text
text(node,exp){
    this.update(node,exp,'text')
}textUpdater(node,value){
    node.textContent=value
}
{{xxx}}
compileText(node){
    this.update(node,RegExp.$1,'text')
}

dep,依赖创建,dep和watcher建立联系

//依赖:defineReactive中的每一个key对应一个dep实例class Dep{
    constructor(){
        this.deps=[]
    }
    addDep(watcher){
        this.deps.push(watcher)
    }
    notify(){
        this.deps.forEach(watcher=>{
            watcher.update()
        })
    }
}
defineReactive中的每一个key对应一个dep实例所以在里边加 const dep=new Dep()
watcher的构造函数中
 //创建实例时,把当前实例指定到dep.target静态属性上
 Dep.target=this
 //读一下key,触发get
 vm[key]
 //置空
 Dep.target=nulldefineReactive的get方法中
dep.target&&dep.addDep(Dep.target)
defineReactive的set方法中通知更新
dep.notify()