vue源码浅析(对象和数组依赖处理)

前端之家收集整理的这篇文章主要介绍了vue源码浅析(对象和数组依赖处理)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
  1. //先看一例子
  2.  
  3. importVuefrom'./instance/vue'
  4. letv=newVue({
  5. data:{
  6. a:1,b:{
  7. c:3
  8. }
  9. }
  10. })
  11. console.log(v.b.c);//3
  12.  
  13. v.$watch("b.c",(newVal,oldVal)=>console.log('newVal',newVal,'oldVal',oldVal,'\n'));//1秒后newVal{d:[Getter/Setter]}oldVal3
  14.  
  15. v.$watch("b.c.d",'\n'))//2秒后newVal5oldVal4
  16.  
  17.  
  18. setTimeout(()=>{
  19. v.b.c={d:4};
  20. },1000)
  21.  
  22. setTimeout(()=>{
  23. v.b.c.d=5
  24. },2000)

click here 可运行的 example

(以下都是简化的代码,实际的源码复杂许多)首先是让vue本身对data引用,以及添加$watch方法

  1. importWatcherfrom'../watcher'
  2. import{observe}from"../observer"
  3. exportdefaultclassVue{
  4. constructor(options={}){
  5. this.$options=options
  6. letdata=this._data=this.$options.data
  7.  
  8. //shallowiteratethroughkeystogivevuedirectreference
  9. Object.keys(data).forEach(key=>this._proxy(key))
  10.  
  11. //deepiteratethroughallkeystoproxyallkeys
  12. observe(data,this)
  13. }
  14.  
  15. $watch(expOrFn,cb,options){
  16. newWatcher(this,expOrFn,cb)
  17. }
  18. _proxy(key){
  19. varself=this
  20.  
  21. Object.defineProperty(self,key,{
  22. configurable:true,enumerable:true,get:functionproxyGetter(){
  23. returnself._data[key]
  24. },set:functionproxySetter(val){
  25. self._data[key]=val
  26. }
  27. })
  28. }
  29.  
  30. }

接着实现深度代理函数observe,递归代理所有属性,从而监测所有属性的变化:

  1. import{def}from"../util"
  2. importDepfrom"./dep"
  3. exportdefaultclassObserver{
  4. constructor(value){
  5. this.value=value
  6. this.walk(value)
  7. }
  8. //deepobserveeachkey
  9. walk(value){
  10. Object.keys(value).forEach(key=>this.convert(key,value[key]))
  11. }
  12. convert(key,val){
  13. defineReactive(this.value,val)
  14. }
  15. }
  16. exportfunctiondefineReactive(obj,val){
  17. vardep=newDep()
  18.  
  19. //recursiveobserveallkeys
  20. varchildOb=observe(val)
  21.  
  22. Object.defineProperty(obj,{
  23. enumerable:true,configurable:true,get:()=>{
  24. //checkdependencytosubscribe
  25. if(Dep.target){
  26. dep.addSub(Dep.target)
  27. }
  28. returnval
  29. },set:newVal=>{
  30. varvalue=val
  31. if(newVal===value){
  32. return
  33. }
  34. val=newVal
  35. //checkchangetoreobserveandpublishalldependencies
  36. childOb=observe(newVal)
  37. dep.notify()
  38. }
  39. })
  40. }
  41.  
  42. exportfunctionobserve(value,vm){
  43. if(!value||typeofvalue!=='object'){
  44. return
  45. }
  46. returnnewObserver(value)
  47. }

实现依赖处理:

  1. //import{toArray}from'../util/index'
  2. letuid=0
  3. /**
  4. *Adepisanobservablethatcanhavemultiple
  5. *directivessubscribingtoit.
  6. *
  7. *@constructor
  8. */
  9. exportdefaultfunctionDep(){
  10. this.id=uid++
  11. this.subs=[]
  12. }
  13. //thecurrenttargetwatcherbeingevaluated.
  14. //thisisgloballyuniquebecausetherecouldbeonlyone
  15. //watcherbeingevaluatedatanytime.
  16. Dep.target=null
  17. /**
  18. *Addadirectivesubscriber.
  19. *
  20. *@param{Directive}sub
  21. */
  22. Dep.prototype.addSub=function(sub){
  23. this.subs.push(sub)
  24. }
  25. /**
  26. *Removeadirectivesubscriber.
  27. *
  28. *@param{Directive}sub
  29. */
  30. Dep.prototype.removeSub=function(sub){
  31. //this.subs.$remove(sub)
  32. }
  33. /**
  34. *Addselfasadependencytothetargetwatcher.
  35. */
  36. Dep.prototype.depend=function(){
  37. Dep.target.addDep(this)
  38. }
  39. /**
  40. *Notifyallsubscribersofanewvalue.
  41. */
  42. Dep.prototype.notify=function(){
  43. //stablizethesubscriberlistfirst
  44. varsubs=(this.subs)
  45. for(vari=0,l=subs.length;i<l;i++){
  46. subs[i].update()
  47. }
  48. }

实现watch:

  1. importDepfrom'./observer/dep'
  2. exportdefaultclassWatcher{
  3. constructor(vm,cb){
  4. this.cb=cb
  5. this.vm=vm
  6. this.expOrFn=expOrFn
  7. this.value=this.get()
  8. }
  9. update(){
  10. this.run()
  11. }
  12. run(){
  13. constvalue=this.get()
  14. if(value!==this.value){//checkisEqualifnotthencallback
  15. this.cb.call(this.vm,value,this.value)
  16. this.value=value
  17. }
  18. }
  19. addDep(dep){
  20. dep.addSub(this)
  21. }
  22. get(){
  23.  
  24. this.beforeGet();
  25. console.log('\n','watchget');
  26. //fnorexpr
  27. varres=this.vm._data,key=[];
  28. console.log('expOrFn',this.expOrFn)
  29.  
  30. //towatchinstancelikea.b.c
  31. if(typeofthis.expOrFn=='string'){
  32. this.expOrFn.split('.').forEach(key=>{
  33. res=res[key]//eachwillinvokegetter,sinceDep.targetistrue,thiswillsurelyaddthisintodep
  34. })
  35. }
  36.  
  37. this.afterGet();
  38. returnres
  39. }
  40. }
  41.  
  42.  
  43. /**
  44. *Preparefordependencycollection.
  45. */
  46. Watcher.prototype.beforeGet=function(){
  47. Dep.target=this;
  48. };
  49. /**
  50. *Cleanupfordependencycollection.
  51. */
  52. Watcher.prototype.afterGet=function(){
  53. Dep.target=null;
  54. };

以上示例是可以无依赖直接运行的。

接下来是vue的源码片段;

一般对象的处理可以直接代理defineProperty就可以了,不过对于Array的各种操作就不管用了,所以vue进行了基本数组方法代理:

  1. functiondef(obj,val,enumerable){
  2. Object.defineProperty(obj,{
  3. value:val,enumerable:!!enumerable,writable:true,configurable:true
  4. })
  5. }
  6.  
  7. functionindexOf(arr,obj){
  8. vari=arr.length
  9. while(i--){
  10. if(arr[i]===obj)returni
  11. }
  12. return-1
  13. }
  14.  
  15. constarrayProto=Array.prototype
  16. exportconstarrayMethods=Object.create(arrayProto)
  17.  
  18. ;[
  19. 'push','pop','shift','unshift','splice','sort','reverse'
  20. ]
  21. .forEach(function(method){
  22. //cacheoriginalmethod
  23. varoriginal=arrayProto[method]
  24. def(arrayMethods,method,functionmutator(){
  25. //avoidleakingarguments:
  26. //http://jsperf.com/closure-with-arguments
  27. vari=arguments.length
  28. varargs=newArray(i)
  29. while(i--){
  30. args[i]=arguments[i]
  31. }
  32. varresult=original.apply(this,args)
  33. varob=this.__ob__
  34. varinserted
  35. switch(method){
  36. case'push':
  37. inserted=args
  38. break
  39. case'unshift':
  40. inserted=args
  41. break
  42. case'splice':
  43. inserted=args.slice(2)
  44. break
  45. }
  46. //sameasobjectchange
  47. //checkchangetoreobserveandpublishalldependencies
  48. if(inserted)ob.observeArray(inserted)
  49. //notifychange
  50. ob.dep.notify()
  51. returnresult
  52. })
  53. })
  54.  
  55.  
  56. //es5defineProperty对于数组的某些操作和属性(如:length)变化代理有问题,所以需要使用定义的方法操作
  57. 具体原因看http://www.cnblogs.com/ziyunfei/archive/2012/11/30/2795744.html和
  58. http://wiki.jikexueyuan.com/project/vue-js/practices.html
  59.  
  60. def(
  61. arrayProto,'$set',function$set(index,val){
  62. if(index>=this.length){
  63. this.length=Number(index)+1
  64. }
  65. returnthis.splice(index,1,val)[0]//在里面还是通过代理方法splice实现
  66. }
  67. )
  68.  
  69. def(
  70. arrayProto,'$remove',function$remove(item){
  71. /*istanbulignoreif*/
  72. if(!this.length)return
  73. varindex=indexOf(this,item)
  74. if(index>-1){
  75. returnthis.splice(index,1)
  76. }
  77. }
  78. )


看实际的observer干了什么:

  1. constarrayKeys=Object.getOwnPropertyNames(arrayMethods)
  2.  
  3.  
  4.  
  5. exportfunctionObserver(value){
  6. this.value=value
  7. this.dep=newDep()
  8. def(value,'__ob__',this)
  9. if(isArray(value)){
  10. varaugment=hasProto//hasProo='__prop__'in{};__prop__只在某些浏览器才暴漏出来
  11. ?protoAugment//直接将value.__proto__=src
  12. :copyAugment//直接改变本身方法
  13. augment(value,arrayMethods,arrayKeys)
  14. this.observeArray(value)
  15. }else{
  16. this.walk(value)
  17. }
  18. }
  19.  
  20.  
  21.  
  22. functioncopyAugment(target,src,keys){
  23. for(vari=0,l=keys.length;i<l;i++){
  24. varkey=keys[i]
  25. def(target,src[key])
  26. }
  27. }
  28. functionprotoAugment(target,src){
  29. target.__proto__=src
  30. }

总的来说,vue是代理了原始数据的增删改查,从而进行事件订阅和发布等操作,从而控制数据流。

heavily inspired by:https://segmentfault.com/a/1190000004384515

猜你在找的设计模式相关文章