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
}
}