Vue可以说存在三种watcher
-
第一种是维持数据响应式的
render watcher; -
第二种是
computed watcher,是computed函数在自身内部维护的一个watcher,配合其内部的属性dirty开关来决定computed的值是需要重新计算还是直接复用之前的值; -
第三种就是
watcher api了,就是用户自定义的export导出对象的watch属性当然实际上他们都是通过
class Watcher类来实现的
Vue 的响应式原理(render watch)
Vue 的响应式系统通过观察者模式(Observer Pattern)实现,当数据发生变化时,依赖于这些数据的地方(如模板渲染或计算属性)会被通知并更新。
Observer: 这里的主要工作是递归地监听对象上的所有属性,在属性值改变的时候,触发相应的Watcher。Watcher: 观察者,当监听的数据值修改时,执行响应的回调函数,在Vue里面的更新模板内容。Dep: 链接Observer和Watcher的桥梁,每一个Observer对应一个Dep,它内部维护一个数组,保存与该Observer相关的Watcher。
-
Observer 对象:(初始化时对对象属性的依赖追踪)
__ob__:当 Vue 初始化数据时,每个响应式对象都会被挂载一个__ob__属性(Observer 实例),这个属性指向对象的Observer实例。Observer会遍历对象的属性,并将这些属性转化为响应式,即使用Object.defineProperty将每个属性的 getter 和 setter 拦截,并在对象属性更新时,触发与该属性相关的watcher进行更新。function __observe(obj){ for(let item in obj){ let dep = new __dep(); //建立Dep ,管理和watch的连接 let value = obj[item]; if (Object.prototype.toString.call(value) === "[object Object]") __observe(value); Object.defineProperty(obj, item, { configurable: true, enumerable: true, get: function reactiveGetter() { if(__dep.target) dep.addSub(__dep.target); //添加watch依赖 return value; }, set: function reactiveSetter(newVal) { if (value === newVal) return value; value = newVal; dep.notifyAll(); } }); } return obj; }- getter:当访问该属性时,会触发 getter,Vue 会进行依赖收集。此时,当前的
watcher(如模板渲染的 watcher)会被添加到这个属性的Dep中,表示该watcher依赖于这个属性。 - setter:当修改该属性时,会触发 setter,并通过
dep.notify()通知所有依赖这个属性的watcher,使其更新。
2.Dep 对象:
Dep:每个响应式属性(或依赖)都有一个对应的Dep实例。这个Dep用来收集依赖该属性的所有watcher,并在属性变化时通知它们function __dep(){ this.subscribers = []; this.addSub = function(watcher){ if(__dep.target && !this.subscribers.includes(__dep.target) ) this.subscribers.push(watcher); //如果检测到有新增的watch, 添加 } this.notifyAll = function(){ this.subscribers.forEach( watcher => watcher.update()); } } - getter:当访问该属性时,会触发 getter,Vue 会进行依赖收集。此时,当前的
当响应式数据的 setter 被触发(例如用户改变数据),该属性的 Dep 会调用 notify 方法,通知所有收集的 watcher,执行它们的 update 方法,重新渲染或执行其他更新操作
3. Watcher 与 Dep 的关联
在 Vue 中,watcher 是观察者对象,它会订阅多个响应式属性。当某个响应式属性改变时,watcher 会被通知,执行对应的更新操作。
- 当
watcher被创建时,会触发与它相关的依赖属性的 getter,从而让这些属性的Dep收集这个watcher。 - 例如,当渲染模板时,一个
render watcher被创建,模板中用到的所有数据属性都会触发 getter,并将render watcher记录到这些属性的Dep中。
function __watcher(fn){
this.update = function(){
fn();
}
this.activeRun = function(){
__dep.target = this;
fn();
__dep.target = null;
}
this.activeRun();
}
总结流程
-
初始化响应式对象:
- Vue 在初始化时,使用
Observer将对象转为响应式,并通过__ob__标记这个对象已经被观察。
- Vue 在初始化时,使用
-
依赖收集:
- 每个响应式属性的
getter被调用时,Vue 会将当前的watcher(如渲染watcher)添加到该属性的Dep中。此时,watcher就依赖了该属性。
- 每个响应式属性的
-
属性更新:
- 当响应式属性的
setter被调用时,Vue 会触发该属性的Dep.notify(),通知所有依赖这个属性的watcher,并执行更新逻辑。
- 当响应式属性的
简化代码示意:
<!DOCTYPE html>
<html>
<head>
<title>数据绑定</title>
</head>
<body>
<div id="app">
<div>{{msg}}</div>
<div>{{date}}</div>
</div>
</body>
<script type="text/javascript">
var Mvvm = function(config) {
this.$el = config.el;
this.__root = document.querySelector(this.$el);
this.__originHTML = this.__root.innerHTML;
function __dep(){
this.subscribers = [];
this.addSub = function(watcher){
if(__dep.target && !this.subscribers.includes(__dep.target) ) this.subscribers.push(watcher);
}
this.notifyAll = function(){
this.subscribers.forEach( watcher => watcher.update());
}
}
function __observe(obj){
for(let item in obj){
let dep = new __dep();
let value = obj[item];
if (Object.prototype.toString.call(value) === "[object Object]") __observe(value);
Object.defineProperty(obj, item, {
configurable: true,
enumerable: true,
get: function reactiveGetter() {
if(__dep.target) dep.addSub(__dep.target);
return value;
},
set: function reactiveSetter(newVal) {
if (value === newVal) return value;
value = newVal;
dep.notifyAll();
}
});
}
return obj;
}
this.$data = __observe(config.data);
function __proxy (target) {
for(let item in target){
Object.defineProperty(this, item, {
configurable: true,
enumerable: true,
get: function proxyGetter() {
return this.$data[item];
},
set: function proxySetter(newVal) {
this.$data[item] = newVal;
}
});
}
}
__proxy.call(this, config.data);
function __watcher(fn){
this.update = function(){
fn();
}
this.activeRun = function(){
__dep.target = this;
fn();
__dep.target = null;
}
this.activeRun();
}
new __watcher(() => {
console.log(this.msg, this.date);
})
new __watcher(() => {
var html = String(this.__originHTML||'').replace(/"/g,'\"').replace(/\s+|\r|\t|\n/g, ' ')
.replace(/{{(.)*?}}/g, function(value){
return value.replace("{{",'"+(').replace("}}",')+"');
})
html = `var targetHTML = "${html}";return targetHTML;`;
var parsedHTML = new Function(...Object.keys(this.$data), html)(...Object.values(this.$data));
this.__root.innerHTML = parsedHTML;
})
}
var vm = new Mvvm({
el: "#app",
data: {
msg: "1",
date: new Date(),
obj: {
a: 1,
b: 11
}
}
})
</script>
</html>
上面主要是响应式数据的实现,watch为render watch 除此之外 ,watch还有两种
computed watcher
computed函数在自身内部维护的一个watcher,配合其内部的属性dirty开关来决定computed的值是需要重新计算还是直接复用之前的值。
computed计算属性可以定义两种方式的参数,{ [key: string]: Function | { get: Function, set: Function } }
计算属性直接定义在Vue实例中,所有getter和setter的this上下文自动地绑定为Vue实例
此外如果为一个计算属性使用了箭头函数,则this不会指向这个组件的实例,不过仍然可以将其实例(vn)作为函数的第一个参数来访问,计算属性的结果会被缓存,除非依赖的响应式property变化才会重新计算,注意如果某个依赖例如非响应式property在该实例范畴之外,则计算属性是不会被更新的。
watcher api
对于watch api,类型{ [key: string]: string | Function | Object | Array }
Vue实例将会在实例化时调用$watch(),遍历watch对象的每一个property
在watch api中可以定义deep与immediate属性,分别为深度监听watch和最初绑定即执行回调的定义,在render watch中定义数组的每一项由于性能与效果的折衷是不会直接被监听的,但是使用deep就可以对其进行监听
当然在Vue3中使用Proxy就不存在这个问题了,这原本是Js引擎的内部能力,拦截行为使用了一个能够响应特定操作的函数,即通过Proxy去对一个对象进行代理之后,我们将得到一个和被代理对象几乎完全一样的对象,并且可以从底层实现对这个对象进行完全的监控
不应该使用箭头函数来定义watcher函数,例如searchQuery: newValue => this.updateAutocomplete(newValue),理由是箭头函数绑定了父级作用域的上下文,所以this将不会按照期望指向Vue实例,this.updateAutocomplete将是undefined
watch: {
a: function(n, o){ // 普通watcher
console.log("a", o, "->", n);
},
b: { // 可以指定immediate属性
handler: function(n, o){
console.log("b", o, "->", n);
},
immediate: true
},
c: [ // 逐单元执行
function handler(n, o){
console.log("c1", o, "->", n);
},{
handler: function(n, o){
console.log("c2", o, "->", n);
},
immediate: true
}
],
d: {
handler: function(n, o){ // 因为是内部属性值 更改不会执行
console.log("d.e1", o, "->", n);
},
},
"d.e": { // 可以指定内部属性的值
handler: function(n, o){
console.log("d.e2", o, "->", n);
}
},
f: { // 深度绑定内部属性
handler: function(n){
console.log("f.g", n.g);
},
deep: true
}
}