前言
本文主要分析vue2源码中的数据劫持、响应式,围绕他们的设计思想和源码解析.
通过这篇文章可以学习到什么呢?
1.数据劫持源码的整体实现思路
2.什么是代理模式
3.响应式源码的整体实现思路
4.什么是发布订阅模式
什么是代理模式
为其他对象提供一种代理以控制对这个对象的访问
在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用
vue2中使用Object.defineProperty作为中间代理,并分别设定get、set方法实现功能解耦
数据劫持
在vue2和vue3中数据劫持分别使用了object.defineProperty和proxy,并使用代理模式思想.
第一步:初始化
function initData(){
let data = vm.$options.data;
data = isFunction(data)?data.call(vm,vm):data;
const ob = observe(data);
}
第二步:开始数据劫持
function observe(value){
return new Observer(value);
}
class Observer{
dep;
costructor(value,shallow = false,mock = false){
this.dep = new Dep();
if(isArray(value)){
value.proto = arrayMethods;
this.observeArray(value);
}else{
const keys = Object.keys(value);
for(let i=0;i<keys.length;i++){
const key = keys[i];
defineReactive(value,key,shallow,mock);
}
}
}
observeArray(value){
for(let i=0,l=value.length;i<l;i++){
observe(value[i]);
}
}
}
function defineReactive(obj,key,shallow,mock){
const dep = new Dep();
let childOb = !shallow && observe(val,false,mock);
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get:function reactiveGetter(){
dep.depend({
target:obj,
type:'get',
key,
})
},
set:function reactiveSetter(newVal){
dep.notify({
type:'set',
target:obj,
key,
newValue,
oldValue:value
})
}
})
}
订阅发布模式
发布-订阅模式定义了一种一对多的关系,通过一个中间者让多个订阅者对象同时监听某一个发布者
stateDiagram-v2
observe --> dep
dep --> watcher1
dep --> watcher2
watcher1 --> dep
watcher2 --> dep
响应式原理
将observe作为发布者,dep作为中间者,watcher作为订阅者
1.发布者通过Object.defineProperty的set和get去通知中间者进行depend依赖自己和notify通知订阅中间者的订阅者进行更新update
2.中间者
通过this.subs数组来存储订阅者,
通过addSub方法去添加,
通过removeSub方法去移除
3.订阅者
通过this.deps数组来存储需要订阅的中间者
通过depend方法直接
通过addDep方法直接订阅到特定的中间者dep上,
通过cleanupDeps方法清除订阅不同的dep,
通过update方法产生更新,
通过depend方法遍历this.deps数组,通知dep去订阅
实现代码
class Dep{
static target;
id;
subs;
_pending = false;
constructor(){
this.id = uis++;
this.subs = [];
}
addSub(sub){
this.subs.push(sub);
}
removeSub(sub:DepTarget){
this.subs[this.subs.indexOf(subs)] = null;
}
depend(info){
Dep.target.addDep(this);
Dep.target.onTrack({
effect: Dep.target,
...info
})
}
notify(info){
const subs = this.subs.filter(s=>s);
for(let i = 0,l = subs.length;i<l;i++){
const sub = subs[i];
sub.update();
}
}
}
class Watcher{
addDep(dep){
dep.addSub(this);
}
cleanupDeps(){
let i = this.deps.length;
while(i--){
const dep = this.deps[i];
dep.removeSub(this);
}
}
update(){
}
depend(){
let i = this.deps.length;
while(i--){
this.deps[i].depend();
}
}
}