响应式系统
先阅读一遍官方文档的这一节 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/settersvar 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 setterif (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// 核心就是创建个watcherconst watcher = new Watcher(this, exprOrFn, cb, options);if(options.immediate){cb.call(vm,watcher.value)}}
computed watcher
function initComputed(vm, computed) {// 存放计算属性的watcherconst watchers = vm._computedWatchers = {};for (const key in computed) {const userDef = computed[key];// 获取get方法const getter = typeof userDef === 'function' ? userDef : userDef.get;// 创建计算属性watcherwatchers[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一次}// 如果计算属性在模板中使用,就让计算属性中依赖的数据也记录渲染watcherif (Dep.target) {watcher.depend();}return watcher.value}}}
每个计算属性都是一个watcher,且 lazy=true,初始化watcher时不会立即调用get方法
- dirty 用来缓存,一开始为true,get时会计算出值,并置为 false;如果依赖的数据变化,则dirty置为true,get 时会重新触发计算。
 - 如果计算属性在模板中使用,就让计算属性 watcher 中依赖的数据也记录渲染 watcher
 
