- //先看一例子
- importVuefrom'./instance/vue'
- letv=newVue({
- data:{
- a:1,b:{
- c:3
- }
- }
- })
- console.log(v.b.c);//3
- v.$watch("b.c",(newVal,oldVal)=>console.log('newVal',newVal,'oldVal',oldVal,'\n'));//1秒后newVal{d:[Getter/Setter]}oldVal3
- v.$watch("b.c.d",'\n'))//2秒后newVal5oldVal4
- setTimeout(()=>{
- v.b.c={d:4};
- },1000)
- setTimeout(()=>{
- v.b.c.d=5
- },2000)
(以下都是简化的代码,实际的源码复杂许多)首先是让vue本身对data引用,以及添加$watch方法:
- importWatcherfrom'../watcher'
- import{observe}from"../observer"
- exportdefaultclassVue{
- constructor(options={}){
- this.$options=options
- letdata=this._data=this.$options.data
- //shallowiteratethroughkeystogivevuedirectreference
- Object.keys(data).forEach(key=>this._proxy(key))
- //deepiteratethroughallkeystoproxyallkeys
- observe(data,this)
- }
- $watch(expOrFn,cb,options){
- newWatcher(this,expOrFn,cb)
- }
- _proxy(key){
- varself=this
- Object.defineProperty(self,key,{
- configurable:true,enumerable:true,get:functionproxyGetter(){
- returnself._data[key]
- },set:functionproxySetter(val){
- self._data[key]=val
- }
- })
- }
- }
接着实现深度代理函数observe,递归代理所有属性,从而监测所有属性的变化:
- import{def}from"../util"
- importDepfrom"./dep"
- exportdefaultclassObserver{
- constructor(value){
- this.value=value
- this.walk(value)
- }
- //deepobserveeachkey
- walk(value){
- Object.keys(value).forEach(key=>this.convert(key,value[key]))
- }
- convert(key,val){
- defineReactive(this.value,val)
- }
- }
- exportfunctiondefineReactive(obj,val){
- vardep=newDep()
- //recursiveobserveallkeys
- varchildOb=observe(val)
- Object.defineProperty(obj,{
- enumerable:true,configurable:true,get:()=>{
- //checkdependencytosubscribe
- if(Dep.target){
- dep.addSub(Dep.target)
- }
- returnval
- },set:newVal=>{
- varvalue=val
- if(newVal===value){
- return
- }
- val=newVal
- //checkchangetoreobserveandpublishalldependencies
- childOb=observe(newVal)
- dep.notify()
- }
- })
- }
- exportfunctionobserve(value,vm){
- if(!value||typeofvalue!=='object'){
- return
- }
- returnnewObserver(value)
- }
实现依赖处理:
- //import{toArray}from'../util/index'
- letuid=0
- /**
- *Adepisanobservablethatcanhavemultiple
- *directivessubscribingtoit.
- *
- *@constructor
- */
- exportdefaultfunctionDep(){
- this.id=uid++
- this.subs=[]
- }
- //thecurrenttargetwatcherbeingevaluated.
- //thisisgloballyuniquebecausetherecouldbeonlyone
- //watcherbeingevaluatedatanytime.
- Dep.target=null
- /**
- *Addadirectivesubscriber.
- *
- *@param{Directive}sub
- */
- Dep.prototype.addSub=function(sub){
- this.subs.push(sub)
- }
- /**
- *Removeadirectivesubscriber.
- *
- *@param{Directive}sub
- */
- Dep.prototype.removeSub=function(sub){
- //this.subs.$remove(sub)
- }
- /**
- *Addselfasadependencytothetargetwatcher.
- */
- Dep.prototype.depend=function(){
- Dep.target.addDep(this)
- }
- /**
- *Notifyallsubscribersofanewvalue.
- */
- Dep.prototype.notify=function(){
- //stablizethesubscriberlistfirst
- varsubs=(this.subs)
- for(vari=0,l=subs.length;i<l;i++){
- subs[i].update()
- }
- }
实现watch:
- importDepfrom'./observer/dep'
- exportdefaultclassWatcher{
- constructor(vm,cb){
- this.cb=cb
- this.vm=vm
- this.expOrFn=expOrFn
- this.value=this.get()
- }
- update(){
- this.run()
- }
- run(){
- constvalue=this.get()
- if(value!==this.value){//checkisEqualifnotthencallback
- this.cb.call(this.vm,value,this.value)
- this.value=value
- }
- }
- addDep(dep){
- dep.addSub(this)
- }
- get(){
- this.beforeGet();
- console.log('\n','watchget');
- //fnorexpr
- varres=this.vm._data,key=[];
- console.log('expOrFn',this.expOrFn)
- //towatchinstancelikea.b.c
- if(typeofthis.expOrFn=='string'){
- this.expOrFn.split('.').forEach(key=>{
- res=res[key]//eachwillinvokegetter,sinceDep.targetistrue,thiswillsurelyaddthisintodep
- })
- }
- this.afterGet();
- returnres
- }
- }
- /**
- *Preparefordependencycollection.
- */
- Watcher.prototype.beforeGet=function(){
- Dep.target=this;
- };
- /**
- *Cleanupfordependencycollection.
- */
- Watcher.prototype.afterGet=function(){
- Dep.target=null;
- };
以上示例是可以无依赖直接运行的。
接下来是vue的源码片段;
一般对象的处理可以直接代理defineProperty就可以了,不过对于Array的各种操作就不管用了,所以vue进行了基本数组方法代理:
- functiondef(obj,val,enumerable){
- Object.defineProperty(obj,{
- value:val,enumerable:!!enumerable,writable:true,configurable:true
- })
- }
- functionindexOf(arr,obj){
- vari=arr.length
- while(i--){
- if(arr[i]===obj)returni
- }
- return-1
- }
- constarrayProto=Array.prototype
- exportconstarrayMethods=Object.create(arrayProto)
- ;[
- 'push','pop','shift','unshift','splice','sort','reverse'
- ]
- .forEach(function(method){
- //cacheoriginalmethod
- varoriginal=arrayProto[method]
- def(arrayMethods,method,functionmutator(){
- //avoidleakingarguments:
- //http://jsperf.com/closure-with-arguments
- vari=arguments.length
- varargs=newArray(i)
- while(i--){
- args[i]=arguments[i]
- }
- varresult=original.apply(this,args)
- varob=this.__ob__
- varinserted
- switch(method){
- case'push':
- inserted=args
- break
- case'unshift':
- inserted=args
- break
- case'splice':
- inserted=args.slice(2)
- break
- }
- //sameasobjectchange
- //checkchangetoreobserveandpublishalldependencies
- if(inserted)ob.observeArray(inserted)
- //notifychange
- ob.dep.notify()
- returnresult
- })
- })
- //es5defineProperty对于数组的某些操作和属性(如:length)变化代理有问题,所以需要使用定义的方法操作
- 具体原因看http://www.cnblogs.com/ziyunfei/archive/2012/11/30/2795744.html和
- http://wiki.jikexueyuan.com/project/vue-js/practices.html
- def(
- arrayProto,'$set',function$set(index,val){
- if(index>=this.length){
- this.length=Number(index)+1
- }
- returnthis.splice(index,1,val)[0]//在里面还是通过代理方法splice实现
- }
- )
- def(
- arrayProto,'$remove',function$remove(item){
- /*istanbulignoreif*/
- if(!this.length)return
- varindex=indexOf(this,item)
- if(index>-1){
- returnthis.splice(index,1)
- }
- }
- )
看实际的observer干了什么:
- constarrayKeys=Object.getOwnPropertyNames(arrayMethods)
- exportfunctionObserver(value){
- this.value=value
- this.dep=newDep()
- def(value,'__ob__',this)
- if(isArray(value)){
- varaugment=hasProto//hasProo='__prop__'in{};__prop__只在某些浏览器才暴漏出来
- ?protoAugment//直接将value.__proto__=src
- :copyAugment//直接改变本身方法
- augment(value,arrayMethods,arrayKeys)
- this.observeArray(value)
- }else{
- this.walk(value)
- }
- }
- functioncopyAugment(target,src,keys){
- for(vari=0,l=keys.length;i<l;i++){
- varkey=keys[i]
- def(target,src[key])
- }
- }
- functionprotoAugment(target,src){
- target.__proto__=src
- }
总的来说,vue是代理了原始数据的增删改查,从而进行事件订阅和发布等操作,从而控制数据流。
heavily inspired by:https://segmentfault.com/a/1190000004384515