文章目录
- VUE2双向绑定的原理
- 1. 什么是双向绑定
- 2. 双向绑定的原理
- 2.1 ViewModel的重要作用
- 2.2 双向绑定的流程
- 3. 双向绑定的实现
- 3.1 data响应化处理
- 3.2 Compile编译
- 3.3 依赖收集
VUE2双向绑定的原理
1. 什么是双向绑定
- 讲双向绑定先讲单项绑定,啥叫单项绑定,就是一句话就是通过Model去改变View,再直白点,就是通过js代码把数据改变后,html视图也跟着变化
- 那双项绑定就很好理解了,在单项绑定的基础上,如果view改变了,Model也能同步变化
- 一句话概括就是,Model变化View跟着跟新,View跟新,Model跟着变化,这就是双向绑定
2. 双向绑定的原理
- 其实我们可以很容易想到一点就是,如果A变化了想要B跟着变化,最简单的方式就是,A变化的时候通知一下B就行,这就是基本思路
- 在VUE2 中,双向绑定由三个重要部分构成
- 数据层(Model),应用的数据及业务逻辑
- 视图层(View),应用的展示效果,理解为UI组件
- 业务逻辑层(ViewModel),框架封装的核心,他主要负责把数据层和视图层关联起来,这就是MVVM模型
2.1 ViewModel的重要作用
- ViewModel主要干两件事
- 数据变化后,更新视图
- 视图变化后,更新数据
- 那么问题来了,怎么通知呢,我们怎么知道数据变化后,通知哪些视图呢
- 这要依赖ViewModel的两个重要部件
- 监听器(Observer),对所有数据的属性进行监听
- 解析器(Compiler),对元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数
2.2 双向绑定的流程
- 双向绑定的流程
- 我们在new Vue()时,执行初始化,对data执行相应化处理,这个过程发生在Observer中
- 同时对模板执行编译,找到其中动态绑定的数据,从data中获取并初始化视图,这个过程发生在Compiler中
- 同时定义一个更新函数和Watcher,将来对应数据变化时,Watcher会调用更新函数
- 由于data中的数据的某个key可能出现在视图的多处,所以每个key都需要一个管家Dep来管理多个Watcher
- 将来数据一旦发生变化,会首先找到对应的Dep,通过Dep李曼的所有Watcher执行更新函数
3. 双向绑定的实现
3.1 data响应化处理
- 我们来创建一个构造函数,执行初始化,对data数据执行响应化处理
class Vue{
constrcutor(options){
this.$options=options;
this.$data=options.data;
//对data选项做响应式处理
Observe(this.$data)
//代理data到vm上
proxy(this)
// 执行编译
new Compile(options.el,this)
}
}
function Observe(obj){
if(typeof obj!=='object' || obj===null){
return;
}
new Observer(obj)
}
class Observer{
constructor(value){
this.value=value;
this.walk(value)
}
walk(obj){
Object.keys(obj).forEach((key)=>{
defineReactive(obj,key,obj[key])//内部是Object.defineProperty实现,后面会讲
})
}
}
3.2 Compile编译
- 对元素节点的指令机型扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数
class Compile{
constructor(el,vm){
this.$vm=vm;
this.$el=document.querySelector(el);//获取DOM
if(this.$el)this.compile(this.#el)
}
compile(el){
const childNodes=el.ChildNodes;
Array.from(childNodes).forEach((node)=>{//遍历子元素
if(this.isElement(node)){//判断是否为节点
/*编译元素*/
}else if(this.inInterpolation(node)){//是否为差值文本
/*编译差值文本*/
}
if(node.childNodes && node.childNodes.length>0){
this.compile(node)
}
})
}
isElement(node){
return node.nodeType===1
}
isInterpolation(node){
return node.nodeType===3 && /\{\{(.*)\}\}/.test(node.textContent)
}
}
3.3 依赖收集
-
视图中会用到data中的某个key,这被称为依赖,一个key可能出现在视图中的多个位置,每次都需要收集出来用一个Watcher来维护他们,这个过程被称为依赖收集,很多歌Watcher需要一个Dep来管理,需要更新时由Dep统一通知
-
基本思路
- defineReactive为每一个key创建一个Dep,比如data1创建Dep1
- 初始化视图时,读取某个key,例如data1,就创建一个watcher1
- 由于读取key时触发getter方法,边疆watcher1天假到data1的Dep1中份
- 当data1更新时,触发setter,通过Dep1通知所有的watcher更新
class Wacther{
constructor(vm,key,updater){
this.$vm=vm;
this.$key=key;
this.updaterFn=updater;
//创建实例时,把当前实例指定到Dep.target静态属性上
Dep.target=this;
vm[key]//读一下key,触发get
Dep.target=null
update(){
this.updaterFn.call(this.$vm,this.$vm[this.$key])
}
}
}
class Dep{
constructor(){
this.deps=[];//依赖管理
}
addDep(dep){
this.deps.push(dep)
}
notify(){
this.deps.forEach((dep)=>{
dep.update()
})
}
}
// 创建Watcher时触发getter
function defineReactive(obj,key,val){
this.observe(val)
const dep=new Dep()
Object.defineProperty(obj,key,{
get (){
Dep.target && dep.addDep(Dep.target)//Dep.target就是Watcher实例
return val
},
set(newVal){
if(newVal===val)return
dep.notify()//通知dep执行更新方法
}
})
}