参考答案:
我们先从单向绑定切入单向绑定非常简单,就是把Model
绑定到View
,当我们用JavaScript
代码更新Model
时,View
就会自动更新双向绑定就很容易联想到了,在单向绑定的基础上,用户更新了View
,Model
的数据也自动被更新了,这种情况就是双向绑定举个栗子
当用户填写表单时,View
的状态就被更新了,如果此时可以自动更新Model
的状态,那就相当于我们把Model
和View
做了双向绑定关系图如下
我们都知道 Vue
是数据双向绑定的框架,双向绑定由三个重要部分构成
而上面的这个分层的架构方案,可以用一个专业术语进行称呼:MVVM
这里的控制层的核心功能便是 “数据双向绑定” 。自然,我们只需弄懂它是什么,便可以进一步了解数据绑定的原理
它的主要职责就是:
当然,它还有两个主要部分组成
我们还是以Vue
为例,先来看看Vue
中的双向绑定流程是什么的
new Vue()
首先执行初始化,对data
执行响应化处理,这个过程发生Observe
中data
中获取并初始化视图,这个过程发生在Compile
中Watcher
,将来对应数据变化时Watcher
会调用更新函数data
的某个key
在⼀个视图中可能出现多次,所以每个key
都需要⼀个管家Dep
来管理多个Watcher
Dep
,通知所有Watcher
执行更新函数流程图如下:
先来一个构造函数:执行初始化,对data
执行响应化处理
1class Vue { 2 constructor(options) { 3 this.$options = options; 4 this.$data = options.data; 5 6 // 对data选项做响应式处理 7 observe(this.$data); 8 9 // 代理data到vm上 10 proxy(this); 11 12 // 执行编译 13 new Compile(options.el, this); 14 } 15}
对data
选项执行响应化具体操作
1function observe(obj) { 2 if (typeof obj !== "object" || obj == null) { 3 return; 4 } 5 new Observer(obj); 6} 7 8class Observer { 9 constructor(value) { 10 this.value = value; 11 this.walk(value); 12 } 13 walk(obj) { 14 Object.keys(obj).forEach((key) => { 15 defineReactive(obj, key, obj[key]); 16 }); 17 } 18}
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)) { // 判断是否为节点
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);
}
}
视图中会用到data
中某key
,这称为依赖。同⼀个key
可能出现多次,每次都需要收集出来用⼀个Watcher
来维护它们,此过程称为依赖收集多个Watcher
需要⼀个Dep
来管理,需要更新时由Dep
统⼀通知
实现思路
defineReactive
时为每⼀个key
创建⼀个Dep
实例key
,例如name1
,创建⼀个watcher1
name1
的getter
方法,便将watcher1
添加到name1
对应的Dep中name1
更新,setter
触发时,便可通过对应Dep
通知其管理所有Watcher
更新1// 负责更新视图 2class Watcher { 3 constructor(vm, key, updater) { 4 this.vm = vm 5 this.key = key 6 this.updaterFn = updater 7 8 // 创建实例时,把当前实例指定到Dep.target静态属性上 9 Dep.target = this 10 // 读一下key,触发get 11 vm[key] 12 // 置空 13 Dep.target = null 14 } 15 16 // 未来执行dom更新函数,由dep调用的 17 update() { 18 this.updaterFn.call(this.vm, this.vm[this.key]) 19 } 20}
声明Dep
1class Dep { 2 constructor() { 3 this.deps = []; // 依赖管理 4 } 5 addDep(dep) { 6 this.deps.push(dep); 7 } 8 notify() { 9 this.deps.forEach((dep) => dep.update()); 10 } 11}
创建watcher
时触发getter
1class Watcher { 2 constructor(vm, key, updateFn) { 3 Dep.target = this; 4 this.vm[this.key]; 5 Dep.target = null; 6 } 7} 8
依赖收集,创建Dep
实例
1function defineReactive(obj, key, val) { 2 this.observe(val); 3 const dep = new Dep(); 4 Object.defineProperty(obj, key, { 5 get() { 6 Dep.target && dep.addDep(Dep.target);// Dep.target也就是Watcher实例 7 return val; 8 }, 9 set(newVal) { 10 if (newVal === val) return; 11 dep.notify(); // 通知dep执行更新方法 12 }, 13 }); 14}
最近更新时间:2024-08-10
题库维护不易,您的支持就是我们最大的动力!