变化侦测真是很难理解啊,还是需要结合书去看去学习
//dep.js let uid = 0 export class Dep { constructor(){ this.id = uid++ this.subs = [] } addSub(sub){ this.subs.push(sub) } removeSub(sub){ const index = this.subs.indexOf(sub) if(index > -1){ return this.subs.splice(index,1) } } depend(){ if(window.target){ window.target.addDep(this) } } notify(){ const subs = this.subs.slice() for(let i = 0,len = subs.length;i < len;i++){ subs[i].update() } } }
//observer.js mport Dep from "./dep"; function observe(value){ if(!isObject(value)){ return } let ob if(hasOwn(value,'__ob__') && value.__ob__ instanceof Observer){ ob = value.__ob__ }else{ ob = new Observer(value) } return ob } function def(obj,key,val,enumerable){ Object.defineProperty(obj,key,{ value:val, enumerable:!!enumerable, writable:true, configurable:true }) } const arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto) const baseMethods = ['push','pop','shift','unshift','splice','sort','reverse'] baseMethods.forEach(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.splice(2) default: break; } if(inserted){ ob.observeArray(inserted) } ob.dep.notify() return result }) }) function defineReactive(data,key,val){ let childOb = observe(val) let dep = new Dep() Object.defineProperty(data,key,{ enumerable:true, configurable:true, get(){ dep.depend() if(childOb){ childOb.dep.depend() } return val }, set(newVal){ if(val === newVal){ return } val = newVal dep.notify() } }) } 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) } } walk(obj){ const keys = Object.keys(obj) for(let i = 0;i < keys.length;i++){ defineReactive(obj,keys[i],obj[keys[i]]) } } // 侦测Array中的每一项 observeArray(items){ for(let i = 0,len = items.length;i < len;i++){ observe(items[i]) } } }
//watcher.js const reg = /[^w.$]/ function parsePath(path){ if(reg.test(path)){ return } const segments = path.split('.') return function(obj){ for(let i = 0;i < segments.length;i++){ if(!obj){ return } obj = obj[segments[i]] } return obj } } const seenObjects = new Set() function traverse(value){ _traverse(value,seenObjects) seenObjects.clear() } function _traverse(val,seen){ let i,keys const isArray = Array.isArray(val) if((!isArray && !isObject(val)) || Object.isFrozen(val)){ return } if(val.__ob__){ const depId = val.__ob__.dep.id if(seen.has(depId)){ return } seen.add(depId) } if(isArray){ i = val.length while(i--) _traverse(val[i],seen) }else{ keys = Object.keys(val) i = keys.length while(i--) _traverse(val[keys[i]],seen) } } export class Watcher { constructor(vm,expOrFn,cb,options){ this.vm = vm if(options){ this.deep = !!options.deep }else{ this.deep = false } this.deps = [] this.depIds = new Set() if(typeof expOrFn === 'function'){ this.getter = expOrFn }else{ this.getter = parsePath(expOrFn) } this.cb = cb this.value = this.get() } get(){ window.target = this let value = this.getter.call(this.vm,this.vm) if(this.deep){ traverse(value) } window.target = undefined return value } update(){ const oldValue = this.value this.value = this.get() this.cb.call(this.vm,this.value,oldValue) } addDep(dep){ const id = dep.id if(!this.depIds.has(id)){ this.depIds.add(id) this.deps.push(dep) dep.addSub(this) } } teardown(){ let len = this.deps.length while(len--){ this.deps[i].removeSub(this) } } }