响应式原理
vue是一个MVVM框架。
通俗理解,就是通过响应式对象,可以实现页面内容发生变动之后触发数据Model变化,Model发生变化可以触发页面重新渲染。
关于原理有好多文章也讲得很清楚,这里做一下源码的大删除,只保留关键部分易于理解。
简单来说
- 响应式对象会拥有
__ob__属性,用以保存用他自己为入参创建的Observer实例。 - data的每一个属性都拥有自己的Dep实例:dep;
- Dep.target:target是Dep.prototype的属性。这也就意味着每一个Dep实例共用一个Dep.target。同一时间只会有一个watcher实例。
- 读取data[key],会触发这个key的get,此时会判断存在Dep.target的Watcher实例。调用dep.depend,将当前的Watcher塞进dep.subs里。subs会存储所有需要这个值的watcher。
- 修改data[key],会调用dep.notify,即触发每个watcher.update,其实就是watcher.get。
- watcher.get:
1> getter: updateComponent = () => { vm._update(vm._render(), hydrating) }
2> render:生成渲染树,完成对当前vue实例vm的数据访问,触发Observer对data属性做的getter
3> update里面会调用patch :把 VNode 转换成真正的 DOM 节点。页面将会重新渲染。
Observer
Observer会处理data,使其变成响应式对象。
比较直观地感受,如果我们在编写组件时,template里面要用到的数据没有在data里声明,会出现一个报错,Vue 将警告你渲染函数正在试图访问不存在的 property。
class Observer{
constructor(data){
def(value, '__ob__', this)
this.walk(data);
}
walk(obj){
const keys=Object.keys(obj);
for(let i=0;i<keys.length;i++){
defineReactive(obj,keys[i])
}
}
}
function defineReactive(obj,key){
let val=obj[key];
const dep=new Dep();
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get(){
Dep.target&&dep.depend(); // dep.subs.push(Dep.target)
return val;
}
set(newVal){
if(val==newVal) return ;
val=newVal;
dep.notify();
}
})
}
Dep
class Dep{
subs:[],
id;
target,
depend(){
Dep.target.addDep(this);
}
notify(){
for(let i in this.subs){
subs[i].update();
}
}
addSub(watcher){
this.subs.push(watcher);
}
removeSub (sub: Watcher) {
remove(this.subs, sub) // Array.splice
}
}
Watcher
class Watcher{
newDeps:[],
constructor(){
this.get();
}
addDep(dep){
this.newDepIds.add(id)
this.newDeps.push(dep)
dep.addSub(this);
}
update(){
this.run();
}
run(){
this.get();
}
get(){
pushTarget(this); // Dep.target=this;
// getter: updateComponent = () => { vm._update(vm._render(), hydrating) }
// render:生成渲染树,完成对当前vue实例vm的数据访问,触发Observer对data属性做的getter
//update: patch :把 VNode 转换成真正的 DOM 节点
this.getter.call(vm,vm);
popTarget();
}
teardown(){
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
移除监听
- 每一个
Watcher实例会拥有一个数组[Dep],在getter里使用Dep.depend收集依赖时,调用的Watcher.addDep,不仅会对Observer做dep.subs.push(Watcher),也会对当前Watcher.deps.push(dep)。这个Watcher.deps就是收集的依赖。 - 如果希望解除
data[某属性]与watcher的依赖关系,则会调用Watcher.teardown,此时将会对Watcher拥有的deps的每一个dep实例调用removeSub,其实就是使用Array.splice删去当前的Watcher。










网友评论