You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// 源码来源 https://github.com/vuejs/vue/blob/2.6/src/core/observer/index.jsexportclassObserver{value: any;dep: Dep;vmCount: number;// number of vms that have this object as root $dataconstructor(value: any){this.value=valuethis.dep=newDep()this.vmCount=0def(value,'__ob__',this)if(Array.isArray(value)){if(hasProto){protoAugment(value,arrayMethods)}else{copyAugment(value,arrayMethods,arrayKeys)}this.observeArray(value)}else{this.walk(value)}}/** * Walk through all properties and convert them into * getter/setters. This method should only be called when * value type is Object. */walk(obj: Object){constkeys=Object.keys(obj)for(leti=0;i<keys.length;i++){defineReactive(obj,keys[i])}}/** * Observe a list of Array items. */observeArray(items: Array<any>){for(leti=0,l=items.length;i<l;i++){observe(items[i])}}}
// 源码来源 https://github.com/vuejs/vue/blob/2.6/src/core/observer/dep.jsexportdefaultclassDep{statictarget: ?Watcher;id: number;subs: Array<Watcher>;constructor(){this.id=uid++this.subs=[]}addSub(sub: Watcher){this.subs.push(sub)}removeSub(sub: Watcher){remove(this.subs,sub)}depend(){if(Dep.target){Dep.target.addDep(this)}}notify(){// stabilize the subscriber list firstconstsubs=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// ordersubs.sort((a,b)=>a.id-b.id)}for(leti=0,l=subs.length;i<l;i++){subs[i].update()}}}
// 源码来源 https://github.com/vuejs/vue/blob/2.6/src/core/observer/watcher.jsexportdefaultclassWatcher{vm: Component;expression: string;cb: Function;id: number;deep: boolean;user: boolean;lazy: boolean;sync: boolean;dirty: boolean;active: boolean;deps: Array<Dep>;newDeps: Array<Dep>;depIds: SimpleSet;newDepIds: SimpleSet;before: ?Function;getter: Function;value: any;constructor(vm: Component,expOrFn: string|Function,cb: Function,options?: ?Object,isRenderWatcher?: boolean){this.vm=vmif(isRenderWatcher){vm._watcher=this}vm._watchers.push(this)// optionsif(options){this.deep=!!options.deepthis.user=!!options.userthis.lazy=!!options.lazythis.sync=!!options.syncthis.before=options.before}else{this.deep=this.user=this.lazy=this.sync=false}this.cb=cbthis.id=++uid// uid for batchingthis.active=truethis.dirty=this.lazy// for lazy watchersthis.deps=[]this.newDeps=[]this.depIds=newSet()this.newDepIds=newSet()this.expression=process.env.NODE_ENV!=='production'
? expOrFn.toString()
: ''// parse expression for getterif(typeofexpOrFn==='function'){this.getter=expOrFn}else{this.getter=parsePath(expOrFn)if(!this.getter){this.getter=noopprocess.env.NODE_ENV!=='production'&&warn(`Failed watching path: "${expOrFn}" `+'Watcher only accepts simple dot-delimited paths. '+'For full control, use a function instead.',vm)}}this.value=this.lazy
? undefined
: this.get()}/** * Evaluate the getter, and re-collect dependencies. */get(){pushTarget(this)letvalueconstvm=this.vmtry{value=this.getter.call(vm,vm)}catch(e){if(this.user){handleError(e,vm,`getter for watcher "${this.expression}"`)}else{throwe}}finally{// "touch" every property so they are all tracked as// dependencies for deep watchingif(this.deep){traverse(value)}popTarget()this.cleanupDeps()}returnvalue}/** * Add a dependency to this directive. */addDep(dep: Dep){constid=dep.idif(!this.newDepIds.has(id)){this.newDepIds.add(id)this.newDeps.push(dep)if(!this.depIds.has(id)){dep.addSub(this)}}}/** * Clean up for dependency collection. */cleanupDeps(){leti=this.deps.lengthwhile(i--){constdep=this.deps[i]if(!this.newDepIds.has(dep.id)){dep.removeSub(this)}}lettmp=this.depIdsthis.depIds=this.newDepIdsthis.newDepIds=tmpthis.newDepIds.clear()tmp=this.depsthis.deps=this.newDepsthis.newDeps=tmpthis.newDeps.length=0}/** * Subscriber interface. * Will be called when a dependency changes. */update(){/* istanbul ignore else */if(this.lazy){this.dirty=true}elseif(this.sync){this.run()}else{queueWatcher(this)}}/** * Scheduler job interface. * Will be called by the scheduler. */run(){if(this.active){constvalue=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 valueconstoldValue=this.valuethis.value=valueif(this.user){try{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)}}}}/** * Evaluate the value of the watcher. * This only gets called for lazy watchers. */evaluate(){this.value=this.get()this.dirty=false}/** * Depend on all deps collected by this watcher. */depend(){leti=this.deps.lengthwhile(i--){this.deps[i].depend()}}/** * Remove self from all dependencies' subscriber list. */teardown(){if(this.active){// remove self from vm's watcher list// this is a somewhat expensive operation so we skip it// if the vm is being destroyed.if(!this.vm._isBeingDestroyed){remove(this.vm._watchers,this)}leti=this.deps.lengthwhile(i--){this.deps[i].removeSub(this)}this.active=false}}}
The text was updated successfully, but these errors were encountered:
常见 MVC 框架双向绑定方案
实现数据绑定的做法有大致如下几种:
发布者-订阅者模式(backbone.js)
脏值检查(angular.js)
数据劫持(vue.js)
发布者-订阅者模式: 一般通过 sub, pub 的方式实现数据和视图的绑定监听,更新数据方式通常做法是
vm.set('property', value)
,可能我们更希望通过vm.property = value
这种方式更新数据。脏值检查: angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过
setInterval()
定时轮询检测数据变动,angular 只有在指定的事件触发时进入脏值检测,大致如下:**数据劫持: ** vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过
Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应的监听回调。实现过程
实现数据的双向绑定,首先要对数据进行劫持监听,设置一个监听器 Observer,用来监听所有属性。若属性变化,告诉订阅者 Watcher 是否需要更新。实现消息订阅器 Dep 来专门收集这些订阅者,保证监听器 Observer 和订阅者 Watcher 之间进行统一管理的。解析模板指令器 Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者 Watcher,并替换模板数据或者绑定相应的函数,当订阅者 Watcher 接收到相应属性的变化,就会执行对应的更新函数,从而更新视图**(解析模板指令器 Compile 过程中多次操作dom节点,为提高性能和效率,会先将跟节点
el
转换成文档碎片fragment
进行解析编译操作)**。实现数据的双向绑定步骤:实现监听器 Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
实现 Dep, 收集观察依赖(对应订阅者),驱动 Watcher。
实现订阅者 Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
实现解析器 Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器
实现 Observer
用
Obeject.defineProperty()
来监听属性变动, 将需要 Observer 的数据对象进行递归遍历,包括子属性对象的属性。实现 Dep 管理注册订阅
当监听到数据发生变化之后,通知订阅者了,实现一个消息订阅器,维护一个数组,用来收集订阅者,数据变动触发 notify,再调用订阅者的 update 方法:
实现 Watcher
数据变动触发 Dep 收集, notify 通知观察者(watcher) 驱动 update 加到 queueWatcher ,通过钩子( callUpdatedHooks)驱动 updatedQueue
The text was updated successfully, but these errors were encountered: