变化侦测
Object 侦测
递归侦测所有key
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
|
export class Observer { constructor (value) { this.value = value if(!Array.isArray(value)) { this.walk(value) } }
walk(obj) { const keys = Object.keys(obj) for (let i = 0; i < key.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } }
function defineReactive(data, key, val) { if (typeof val === 'object') { new Observer(val) } let dep = new Dep() Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function () { dep.depend() return val }, set: function (newVal) { if(val === newVal) { return }
val = newVal dep.notify() } }) }
export default class Dep { constructor () { this.subs = [] } addSub (sub) { this.subs.push(sub) } removeSub (sub) { remove(this.subs, sub) } depend () { if (window.target) { this.addSub(window.target) } } notify () { const subs = this.subs.slice() for (let i = 0, l = subs.length; i < l; i++) { subs[i].update() } } } function remove (arr, item) { if (arr.length) { const index = arr.indexOf(item) if(index > -1) { return arr.splice(index, 1) } } }
export default class Watcher { constructor (vm, expOrFn, cb) { this.vm = vm this.getter = parsePath(expOrFn) this.cb = cb this.value = this.get() } get() { window.target = this let value = this.getter.call(this.vm, this.vm) window.target = undefined return value } update() { const oldValue = this.value this.value = this.get() this.cb.call(this.vm, this.value, oldValue) } }
|
Array 侦测
Array追踪变化的方式和Object不一样.因为它是通过方法改变内容的,所以我们通过创建拦截器去覆盖数组原型的方式来追踪变化.
为了不污染全局Array.prototype,我们在Observer中只针对那些需要侦测变化的数组使用_proto_来覆盖原型方法,但ES6之前并不是标准属性,针对不支持的游览器,直接循环拦截器,把拦截器中的方法直接设置到数组身上拦截Array.prototype上的原生方法.
Array收集依赖的方式和Object一样,都是在getter中收集.但是由于使用依赖的位置不同,数组要在拦截器中向依赖发送消息,所以依赖不能像Object那样保存在defineReactive中,而是把依赖保存在了Observer实例上.
在Observer中,对每个侦测了变化的数据都标上印记_ob_,并把this(Observer实力)保存在_ob_上.主要有两个作用,一方面是为了标记数据是否被侦测到,另一方面可以很方便的通过数据取到_ob_,从而拿到Observer实例上保存的依赖.当拦截到数组发生到变化时,向依赖发送通知.
除侦测数组的自身变化外,数组中元素发生的变化也要侦测.我们在Observer中判断如果当前被侦测的数据是数组,则调用observeArray方法将数组中的每一个元素都转换成响应式并侦测变化.
当用户使用push等方法像数组中新增数据时,新增的数据也要进行变化侦测.如果是push,unshift,splice方法,从参数中将新增数据提取出来,然后使用observeArray对新增数据进行变化侦测.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
| const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto)
;[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ].forEach(function (method) { const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) break
} if (inserted) ob.observeArray(inserted) ob.dep.notify() return result }) })
import { arrayMethods } from './array' const hasProto = '__proto__' in {} const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
function def (obj, key, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }) }
export class Observer { constructor (value) { this.value = value this.dep = new Dep() def(value, '__ob__', this)
if(Array.isArray(value)) { this.observeArray(value) } else { this.walk(value) } }
observeArray (items) { for (let i =0, l = items.length; i < l; i++) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) } } }
function protoAugment (target, src, keys) { target.__proto__ = src } function copyAugment (target, src, keys) { for (let i = 0, l=keys.length; i < l; i++) { const key = keys[i] def(target, key, src[key]) } }
function defineReactive (data, key, val) { let childOb = observe(val) let dep = new Dep() Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function () { dep.depend()
if (childOb) { childOb.dep.depend() } return val }, set: function (newVal) { if(val === newVal) { return } dep.notify() val = newVal } }) }
export function observe (value, asRootData) { if (!isObject(value)) { return } let ob if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else { ob = new Observer(value) } return ob }
|
vm.$watch
vm.$set
vm.$delete