响应式系统
先阅读一遍官方文档的这一节 https://cn.vuejs.org/v2/guide/reactivity.html
Object.defineProperty
首先了解Object.defineProperty
这个 API
Object.defineProperty(obj, prop, descriptor)
configurable | enumerable | value | writable | get | set | |
---|---|---|---|---|---|---|
改变/删除,默认false | 默认false | 默认undefined | 可写 默认false |
默认undefined | 默认undefined | |
数据描述符 | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
存取描述符 | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
操作方法 | 判断方法 | configurable | writable | enumerable | ||
---|---|---|---|---|---|---|
不可扩展,不能加属性 | Object.preventExtensions(obj) | Object.isExtensible(obj) | - | - | - | |
密封,不能加/删 属性 | Object.seal(obj) | Object.isSealed(obj) | ❌ | ✅ | ✅ | |
冻结,不能加/删/改 属性 | Object.freeze(obj) | Object.isFrozen(obj) | ❌ | ❌ | ✅ |
- Object.isFrozen(obj)=ture 则 Object.isSealed(obj)=true, Object.isExtensible(obj) =false
- Object.isSealed(obj)= true 则 Object.isExtensible(obj) =false
无论是不可扩展,密封,还是冻结,都是浅层控制的
通过上面三种方法都可以控制 Vue是否对数据进行响应式数据拦截
Vue 中对数据的劫持:
function defineReactive (obj,key,val) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
var setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
var childOb = observe(val);
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;
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
// #7981: for accessor properties without setter
if (getter && !setter) { return }
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
}
});
}
function observe (value) {
if (!isObject(value) || value instanceof VNode) {
return
}
var ob;
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__;
} else if (
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value);
}
return ob
}
var Observer = function Observer (value) {
this.value = value;
this.dep = new Dep();
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);
}
};
Observer.prototype.walk = function walk (obj) {
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]);
}
};
Observer.prototype.observeArray = function observeArray (items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};
存放 Dep 实例的两个位置
- 在defineReactive 函数的闭包中(getter/setter 中使用了dep实例)
- 在Observer实例中(ob实例就是存在
value.__ob__
上的)
- 第一种情况:dep实例是和对象的某个key的getter/setter 绑定的
- 第二种情况:dep实例是和对象某个key的value(object类型) 绑定的,有两个用处:
- 数组不是通过defineReactive处理的,是通过拦截数组的原型方法,去触发通知,所以dep只能存在数组本身上面。
- 给 data 中的对象新增属性 Vue 是检测不到变化的,需要使用
Vue.$set
,由于属性是刚加的没法触发闭包中的dep通知,那么需要这个 value 本身存在一个dep收集依赖,对进行 set 的时候会触发通知。
Watcher
RenderWatcher
new Watcher(vm, updateComponent, noop/* 表示空函数 */, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
user Watcher
Vue.prototype.$watch = function(exprOrFn, cb, options = {}) {
options.user = true; // 标记为用户watcher
// 核心就是创建个watcher
const watcher = new Watcher(this, exprOrFn, cb, options);
if(options.immediate){
cb.call(vm,watcher.value)
}
}
computed watcher
function initComputed(vm, computed) {
// 存放计算属性的watcher
const watchers = vm._computedWatchers = {};
for (const key in computed) {
const userDef = computed[key];
// 获取get方法
const getter = typeof userDef === 'function' ? userDef : userDef.get;
// 创建计算属性watcher
watchers[key] = new Watcher(vm, userDef, () => {}, { lazy: true });
// 将 computed 属性的 getter/setter 挂载到 vm 上
defineComputed(vm, key, userDef)
}
}
function createComputedGetter (key) {
return function computedGetter () {
var watcher = this._computedWatchers && this._computedWatchers[key];
if (watcher) {
if (watcher.dirty) {
watcher.evaluate(); //get一次
}
// 如果计算属性在模板中使用,就让计算属性中依赖的数据也记录渲染watcher
if (Dep.target) {
watcher.depend();
}
return watcher.value
}
}
}
每个计算属性都是一个watcher,且 lazy=true,初始化watcher时不会立即调用get方法
- dirty 用来缓存,一开始为true,get时会计算出值,并置为 false;如果依赖的数据变化,则dirty置为true,get 时会重新触发计算。
- 如果计算属性在模板中使用,就让计算属性 watcher 中依赖的数据也记录渲染 watcher