一:什么是双向绑定?
先说单向绑定,就是把model绑定到view上面,当我们用js更新model的时候,view会自动更新。
双向数据绑定就是在单向数据绑定的基础上,用户更新了view,model数据也会被更新,这就是双向数据绑定。
二:双向绑定的原理
vue是数据双向绑定的框架,也就是我们常说的MVVM模型。
- 数据层(model):应用的数据及业务逻辑
- 视图层(view):应用的展示效果,各类UI组件
- 业务逻辑层(viewModel):框架封装的核心,负责将数据与视图关联起来
ViewModel的职责:数据变化后更新视图,视图变化后更新数据。
主要由两部分组成:
- 监听器(Observer):对所有的属性进行监听
- 解析器(Compiler):对每个元素节点的指令进行扫描和解析,根据指令模版替换数据,以及绑定相应的更新函数。
三:实现双向绑定
vue双向绑定的流程:
1,new Vue()首先执行初始化,对data执行响应化处理,这个过程在Observe中
2,同时对模版执行编译,找到其中动态绑定的数据,从data中获取,并且初始化视图,这个过程在compile中
3,同时定义一个更新函数和Watcher,将来数据变化时watcher会调用更新函数。
4,由于data的某个key在一个视图中可能出现多次,所有每个key都需要一个管家dep来管理多个watcher。
5,将来data一旦发生变化,首先找到他的管家dep,然后通知所有的watcher执行更新函数。
四:实现
先来一个构造函数,对data进行响应化处理
依赖收集 视图中会用到data的某个key,这称为依赖。同一个key可能出现多次,每次都需要收集出来用一个watcher来维护它们,此过程称为依赖收集。
多个watcher需要一个dep来管理,需要更新时由dep统一通知。
实现思路:
1,defineReactive为每一个key创建一个Dep实例
2,初始化视图的时候读取某个key,例如name1,则创建一个watcher1
3,由于name1触发了getter方法,便将watcher1添加到name1对应的dep中
4,当name1更新,setter触发时,便可通过对应的dep通知其管理所有watcher更新
代码实例:
class Vue {
constructor(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])
})
}
}
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)){ // 判断是否为节点
console.log('编译元素'+node.nodeName)
}else if(this.isInterpolation(node)){
console.log("编译插值⽂本" + node.textContent); // 判断是否为插值文本 {{}}
}
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);
}
}
// 负责更新视图
class Watcher{
constructor(vm, key, updater){
this.vm = vm;
this.key = key;
this.updaterFun = updater
// 创建实例时,把当前实例制定到dep.target静态属性上
Dep.target = this
// 读一下key,触发get
vm[key]
// 置空
Dep.target = null
}
// 未来执行dom更新函数,由dep调用
update(){
this.updaterFun.call(this.vm, this.vm[this.key])
}
}
// 声明dep
class Dep {
constructor(){
this.deps = []
}
addDep(dep){
this.deps.push(dep)
}
notify(){
this.deps.forEach((dep)=>{
dep.update()
})
}
}
// 创建watcher时触发getter
class Watcher {
constructor(vm, key, updateFn){
Dep.target = this
this.vm[this.key]
Dep.target = null
}
}
// 依赖收集,创建Dep实例
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执行更新方法
}
})
}