理解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>
实现原理:
- new vue() 首先执行初始化,对data进行响应化处理,这个过程发生在observe中
- 同时对模版进行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在compile中
- 同时定义一个更新函数和watcher,将来对应数据变化时watcher会更新函数。
- 由于data的某个key在视图中可能出现多次,所以每个key需要一个管家dep来管理多个watcher
- 将来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.el=document.querySelector(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.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()