1.什么是数据的双向绑定
举个例子,用户在界面上指向某些操作,如在输入框中输入文本,数据同步发生改变;同样,通过代码或者其他方式修改数据,界面同步改变。
2.原理
Vue的双向数据绑定是通过数据劫持结合发布订阅者模式实现的。其中在vue2中,数据劫持是通过Object.defineProperty的getter和setter方法在读取和修改数据时候进行拦截实现的;vue3中数据劫持则是通过proxy和reflect结合实现的。
再来谈谈发布订阅者模式,一个消息可以被多个订阅者订阅,一个发布者可以发布多条消息。在数据发生改变时候,发布者会将信息发布给一些相关的订阅者,这些订阅者就会触发对应的监听回调去渲染视图。
3.详细过程
3.1 原理图

3.2 过程
下面来分析一下整个过程(以vue2为例)。
(1)new一个Vue构造实例:
class Vue{
constructor(options) {
//通过传过来的`options`对构造实例进行初始化;
this.$el=document.querySelector(options.el);
this.$data=options.data;
this.$options=options;
this.$watchEvent={}
...
}
}
(2)初始化完成之后,执行observe方法用于对data进行响应式处理
对data的数据进行递归遍历,使用Object.definePropery的getter/setter方法对data中的每个key都进行数据劫持(具体的数据劫持在下面我会详细讲),在setter方法中,会更新视图上涉及到该key变化的所有地方)。
class Vue{
constructor(options) {
//通过传过来的`options`对构造实例进行初始化;
this.$el=document.querySelector(options.el);
this.$data=options.data;
this.$options=options;
this.$watchEvent={}
//处理数据响应式
this.observe();
}
observe(){
//observe方法用于对data进行响应式处理,对data中的数据进行递归遍历
//(下面没有进行递归,因为只是示例代码,不可能那么完善)
//给每个key都加上getter和setter方法。其中在setter方法中,需要去更新视图模板
//上涉及到该key变化的所有地方
for (let key in this.$data){
let value=this.$data[key];
let _this=this;
Object.defineProperty(this.$data,key,{
get(){
return value;
},
set(val) {
value=val;
//更新视图模板上涉及到该key变化的所有地方
_this.$watchEvent[key].forEach((item,index)=>{
item.update()
})
}
})
}
}
}
(3)调用Compiler指令解析指令与模板插值、添加相对应的订阅者
通过递归遍历,从根元素开始遍历,将模板中的变量替换为数据,然后渲染视图。同时给每个节点添加上订阅者。如果收到通知,就会更新视图。
class Vue{
constructor(options) {
//通过传过来的`options`对构造实例进行初始化;
this.$el=document.querySelector(options.el);
this.$data=options.data;
this.$options=options;
this.$watchEvent={}
//处理数据响应式
this.observe();
//调用Compiler指令解析指令与模板插值、添加相对应的订阅者
this.compile(this.$el);
}
//observe方法用于对data进行响应式处理,对data中的数据进行递归遍历
//(下面没有进行递归,因为只是示例代码,不可能那么完善)
//给每个key都加上getter和setter方法。其中在setter方法中,需要去更新视图模板
//上涉及到该key变化的所有地方
observe(){
for (let key in this.$data){
let value=this.$data[key];
let _this=this;
Object.defineProperty(this.$data,key,{
get(){
return value;
},
set(val) {
value=val;
//更新视图模板上涉及到该key变化的所有地方
_this.$watchEvent[key].forEach((item,index)=>{
item.update()
})
}
})
}
}
//通过递归遍历,从根元素开始遍历,将模板中的变量替换为数据,
//然后渲染视图。同时给每个节点添加上订阅者。如果收到通知,就会更新视图。
compile(node){
node.childNodes.forEach( (child,index)=>{
//子节点是元素节点时候
if (child.nodeType==1){
if (child.hasAttribute("@click")){
let eventFuncName=child.getAttribute("@click");
eventFuncName=eventFuncName.trim();
//绑定相关事件
child.addEventListener("click",()=>{
this.$options.methods[eventFuncName].call(this);
})
}
...
if (child.childNodes.length>0){
this.compile(child)
}
}
// 子节点是文本节点时候,
if (child.nodeType==3){
let reg=/\{\{(.*?)\}\}/g
let text=child.textContent;
child.textContent=text.replace(reg,(match,key)=>{
key=key.trim()
//给每个文本节点都添加了一个订阅者
let watcher=new Watch(this,key,child,"textContent")
//依赖收集
if (this.$watchEvent[key]){
this.$watchEvent[key].push(watcher)
}else {
this.$watchEvent[key]=[]
this.$watchEvent[key].push(watcher)
}
return this.$data[key];
})
}
})
}
}
class Watch{
constructor(vm,key,node,attr) {
this.vm=vm;
this.key=key;
this.node=node;
this.attr=attr;
}
update(){
this.node[this.attr]=this.vm[this.key];
}
}
vue3的过程和vue2有些不一样。
- 第一
vue3中不是通过new来初始化vue实例,而是通过createApp函数; - 第二,
vue3中处理响应式不是基于Object.definePropery的getter/setter方法;而是基于es6中的proxy+reflect。
3.3 依赖收集
如上,有一个过程叫依赖收集:vue组件实例中data的key可能在模板中出现多次,每次都需要用一个watcher来维护,这样子才能保证当data中某个key发生改变的时候,模板中依赖该key的所有地方都发生改变。收集这些watcher的过程称为依赖收集,一般在vue组件类的construct构造函数中初期就需要进行依赖收集。多个watcher需要用一个dep进行管理,当某个key发生改变时候,有dep去统一通知。


2352

被折叠的 条评论
为什么被折叠?



