vue响应式原理
注意右侧的调用队列 进行初始化的时候 this._init() 初始化的时候调用 initState() initData() initData中会调用根节点的observe 将数据变成响应式对象
image.png
// initData()中调用根节点的 observe
// observe data
observe(data, true /* asRootData */);
observe只处理 对象
*/
function observe (value, asRootData) {
// 不是对象 或 是 VNode就返回 这是递归的出口
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
// 这是一个observe的自己
ob = value.__ob__;
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 创建Observer的对象
ob = new Observer(value);
}
if (asRootData && ob) {
ob.vmCount++;
}
return ob
}
// Observer 对象
var Observer = function Observer (value) {
this.value = value;
// watch的管理者 收集依赖 和派发更新中使用
this.dep = new Dep();
this.vmCount = 0;
// 定义不可枚举的值 作为标识
def(value, '__ob__', this);
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods);
} else {
copyAugment(value, arrayMethods, arrayKeys);
}
this.observeArray(value);
} else {
this.walk(value);
}
};
// 如果是对象 每个对象中调用 defineReactive
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive$$1(obj, keys[i]);
}
};
// 如果是数组 递归调用observe每一个对象 所以都会走到 defineReactive
Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};
下面来看看defineReactive的代码
function defineReactive$$1 (
obj,
key,
val,
customSetter,
shallow
) {
// Dep是一个watch收集管理器
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
// 如果是不可枚举的 就直接return 所以不可变更对象可以使用Object.frezone
if (property && property.configurable === false) {
return
}
// ...
var childOb = !shallow && observe(val);
// 利用es5的defineProperty 劫持getter/setter getter的时候 通过dep.depend()建立依赖收集 setter的时候dep.notify() 派发更新
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
var value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
set: function reactiveSetter (newVal) {
var value = getter ? getter.call(obj) : val;
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
// 派发更新 执行堆栈dep.notify() watch.update() queueWatcher(this) flushSchedulerQueue() 执行watcher的run方法 如下源码所示
dep.notify
Dep.prototype.notify = function notify () {
// stabilize the subscriber list first
var subs = this.subs.slice();
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort(function (a, b) { return a.id - b.id; });
}
for (var i = 0, l = subs.length; i < l; i++) {
// subs是一个watch的数组
subs[i].update();
}
};
/**
* Flush both queues and run the watchers.
*/
function flushSchedulerQueue () {
currentFlushTimestamp = getNow();
flushing = true;
var watcher, id;
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
queue.sort(function (a, b) { return a.id - b.id; });
// do not cache length because more watchers might be pushed
// as we run existing watchers
for (index = 0; index < queue.length; index++) {
watcher = queue[index];
if (watcher.before) {
watcher.before();
}
id = watcher.id;
has[id] = null;
watcher.run();
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1;
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? ("in watcher with expression \"" + (watcher.expression) + "\"")
: "in a component render function."
),
watcher.vm
);
break
}
}
}
image.png
Watcher.prototype.run = function run () {
if (this.active) {
var value = this.get();
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
var oldValue = this.value;
this.value = value;
if (this.user) {
try {
// 执行用户定义的watch的回调 参数 newValue, oldValue
this.cb.call(this.vm, value, oldValue);
} catch (e) {
handleError(e, this.vm, ("callback for watcher "" + (this.expression) + """));
}
} else {
this.cb.call(this.vm, value, oldValue);
}
}
}
};
// mountComponent vue数据更新驱动视图的原理
// 定义渲染watcher watch的get函数 对应的updateComponent 会改变视图的变化 既是渲染watch初始化的时候被订阅了对应的响应式数据 数据发生了变化会触发这个视图更新
// 这里会执行 watch的run 会执行 this.get 求职 会执行 updateComponent函数 这样会触发视图的更新
简单来说 响应数据发生变化 会触发 defineReactive 的setter中this.get 然后会触发 渲染watch的updateComponent 从而达到视图的变化
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
}
// we set this to vm._watcher inside the watcher's constructor
// since the watcher's initial patch may call $forceUpdate (e.g. inside child
// component's mounted hook), which relies on vm._watcher being already defined
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
hydrating = false;
在vue中, 数据模型下的所有属性,会被Vue使用Object.defineProperty
(Vue3使用Proxy)进行数据劫持代码里。响应式的机制是观察者模式, 数据是被观察的一方, 一旦发生变化, 通知所有的观察者, 这样观察者可以做出响应, 当观察者为视图的时候,会更新视图。
Vue的响应式系统的三个重要概念, Dep
, Observer
, Watcher
.
Dep 调度中心-订阅器
Dep是负责调度和订阅watcher的 dep在getter触发了dep.depend
收集依赖 在setter的时候 触发了dep.notify
派发更新
Observer 发布者
Observer是发布者, 主要作用是vm初始化的时候, 调用defineReactive
函数, 使用Object.defineProperty
方法对对象的每一个子属性进行数据劫持、监听, 就是为每个属性添加上getter
和setter
, 使属性变成响应式。
Watcher-观察者
Watcher 扮演的角色是订阅者/观察者,他的主要作用是为观察属性提供回调函数以及收集依赖,当被观察的值发生变化时,会接收到来自调度中心Dep的通知,从而触发回调函数。
而Watcher又分为三类,normal-watcher
、 computed-watcher
、 render-watcher
。
normal-watcher
:在组件钩子函数watch中定义,即监听的属性改变了,都会触发定义好的回调函数。
computed-watcher
:在组件钩子函数computed中定义的,每一个computed
属性,最后都会生成一个对应的Watcher对象,但是这类Watcher有个特点:当计算属性依赖于其他数据时,属性并不会立即重新计算,只有之后其他地方需要读取属性的时候,它才会真正计算,即具备lazy
(懒计算)特性。
render-watcher
:每一个组件都会有一个render-watcher
, 当data/computed
中的属性改变的时候,会调用该Watcher来更新组件的视图。
这三种Watcher也有固定的执行顺序,分别是:computed-render
-> normal-watcher
-> render-watcher
。这样就能尽可能的保证,在更新组件视图的时候,computed 属性已经是最新值了,如果 render-watcher
排在 computed-render
前面,就会导致页面更新的时候 computed 值为旧数据。
小结
image.pngObserver 负责将数据进行拦截,Watcher 负责订阅,观察数据变化, Dep 负责接收订阅并通知 Observer 和接收发布并通知所有 Watcher。