vue2响应式原理
mvvm模式
defineProperty(响应式实现的基础)
实现:四大核心模块监听器 Observer、订阅器 Dep、订阅者 Watcher、解析器 Compile
监听器 Observer 核心是通过defineReactive实现数据的监听与劫持,在监听与劫持阶段配合Dep与Watcher实现响应式
// 循环遍历数据对象的每个属性
function observable(obj) {
if (!obj || typeof obj !== 'object') {
return;
}
let keys = Object.keys(obj);
keys.forEach((key) => {
defineReactive(obj, key, obj[key])
})
return obj;
}
// 将对象的属性用 Object.defineProperty() 进行设置
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`${key}属性被读取了...`);
return val;
},
set(newVal) {
console.log(`${key}属性被修改了...`);
val = newVal;
}
})
}
订阅器 Dep:依赖收集容器,用来容纳所有的“订阅者”,当数据变化的时候后执行对应订阅者的更新函数
function Dep () {
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
Dep.target = null; //对外暴露Dep.target在后面添加依赖时使用
// 有了Dep之后对defineReactive进行完善设计,get作为收集依赖的入口,set劫持到数据变化时通过dep.notify()通知依赖进行更新
defineReactive: function(data, key, val) {
var dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function getter () {
if (Dep.target) {
dep.addSub(Dep.target);
}
return val;
},
set: function setter (newVal) {
if (newVal === val) {
return;
}
val = newVal;
dep.notify();
}
});
}
订阅者 Watcher:两个核心功能——将自己添加到dep中,确定依赖更新的行为
订阅者 Watcher 初始化的时候触发对应的 get 函数去执行添加订阅者操作,其中Dep.target 用于缓存下订阅者,添加成功后再将其去掉就可以了
function Watcher(vm, exp, cb) {
this.vm = vm;
this.exp = exp;
this.cb = cb;
this.value = this.get(); // 将自己添加到订阅器的操作
}
Watcher.prototype = {
update: function() {
this.run();
},
run: function() {
var value = this.vm.data[this.exp];
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
},
get: function() {
Dep.target = this; // 全局变量 订阅者 赋值
var value = this.vm.data[this.exp] // 强制执行监听器里的get函数
Dep.target = null; // 全局变量 订阅者 释放
return value;
}
};
解析器 Compile:负责初始化Watcher 解析模板指令,并替换模板数据,初始化视图 将模板指令对应的节点绑定对应的更新函数,初始化相应的订阅器
compileText: function(node, exp) {
var self = this;
var initText = this.vm[exp]; // 获取属性值
this.updateText(node, initText); // dom 更新节点文本值
// 将这个指令初始化为一个订阅者,后续 exp 改变时,就会触发这个更新回调,从而更新视图
new Watcher(this.vm, exp, function (value) {
self.updateText(node, value);
});
}
总结: 总体流程:Compile初始化了Watcher并传入了依赖更新的函数,Watcher中将元素添加进了dep中并向外暴露了更新方法,dep与Observer进行绑定,在set阶段进行依赖的更新(get则是依赖收集对外提供的接口)
vue2响应式存在的问题(需要通过原理解释这两个存在的问题、修改和增删)
对象:对象的响应式只针对已经存在的属性,新添加的属性需要使用 this.$set或者Vue.set方法才可以
数组:数组的响应式一般是通过 7 个数组操作方法来实现响应式,像是arr[0] = 1、arr.length = 0这种是没办法实现响应式的
1.Object.defineProperty是可以检测到数组索引的变化的
2.尤大认为性能消耗太大,于是在性能和用户体验之间做了取舍
3.性能消耗体现在无用的数组索引监听
4.解决方案+原理:Vue.set(vm.items, indexOfItem, newValue);核心还是Object.defineProperty。只是尽可能的避免了无用的数组索引监听
但是我们新增一个元素,就不会触发监听事件,因为这个新属性我们并没有监听,删除一个属性也是
关于无法进行数组添加元素问题,
vue3响应式原理
vue3响应式原理由proxy实现,其重要意义就是解决了vue2响应式在执行上的缺陷