Vue响应式原理 在 Vue 2.x 版本中,实现数据双向绑定的主要原理就是通过数据劫持的方式,即Object.defineProperty的getter和setter方法,配合发布-订阅模式,来监听到数据的赋值与变化,从而通知相关的视图进行更新。
那么,具体是怎么实现的呢?
Object.defineProperty首先,我们先了解一下最核心的Object.defineProperty的基本用法:
1 Object.defineProperty(obj, prop, descriptor) 
obj:要定义属性的对象 
prop:要定义或修改的属性的名称或Symbol 
descriptor:要定义或修改的属性描述符,包含以下属性:
configurable:当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为  falseenumerable:当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。默认为 false 。value:该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为undefined 。writable:当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值。默认为false 。get:属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。默认为undefined 。set:属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为undefined 。 
 
 
注意点 : get和set不能和writable及value共存,否则浏览器会报错
1 2 3 Uncaught TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>     at Function.defineProperty (<anonymous>)     at <anonymous>:1:8 
▼简单示例 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 let  obj = {}let  value = null Object .defineProperty(obj, 'property1' , {  set : function (val) {     console .log('set value' )     value = val   },   get : function () {     console .log('get value' )     return  value   } }) obj.property1 = 1  obj.property1 
实例化一个Vue对象 以下面一个简单的Vue实例作为分析参考:
1 2 3 4 5 <div  id ="app" >   <div  @click ="changeMsg" >      {{ message }}   </div >  </div > 
1 2 3 4 5 6 7 8 9 10 11 var  app = new  Vue({  el: '#app' ,   data: {     message: 'Hello Vue!'    },   methods: {     changeMsg () {       this .message = 'Hello World!'      }   } }) 
数据代理 在Vue中,我们定义在data的数据可以通过this.xxx来获取,主要原因在于Vue对data中的数据做了一层数据代理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const  sharedPropertyDefinition = {  enumerable: true ,   configurable: true ,   get : noop,   set : noop } function proxy (target, sourceKey, key) {   sharedPropertyDefinition.get = function  proxyGetter  (     return  this [sourceKey][key]   }   sharedPropertyDefinition.set = function  proxySetter  (val )      this [sourceKey][key] = val   }   Object .defineProperty(target, key, sharedPropertyDefinition) } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function  initData  (vm )   let  data = vm.$options.data   vm._data = data      if  (!isPlainObject(data)) {     data = {}   }   const  keys = Object .keys(data)   let  i = keys.length   while  (i--) {     const  key = keys[i]          proxy(vm, `_data` , key)   }   observe(data, true  ) } 
代理的实现方式并不难,通过Object.defineProperty把target[sourceKey][key]的读写变成了对target[key]的读写。
为什么这样处理呢? 
方便读取 :比起用this._data.xxx来读取,这样的方式更加直接方便保证数据统一 :为了实现this.xxx的方式获取,当然也可以通过for循环把数据一个一个赋值到实例上(methods的处理方式),但是这样做就会导致维护了两份数据,增加了维护的成本不影响依赖的收集与更新 :当对this.xxx进行读取的时候,就会触发_data.xxx中的get与set方法,不会影响到data中数据的依赖收集与更新 
数据劫持 因为我们需要在data数据更新的时候,通知视图的更新,因此我们对data中的每一个数据都生成对应的响应式对象,给对象的每一个属性都加上getter与setter,在getter与setter中插入消息绑定与发布的动作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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)   }   if  (asRootData && ob) {     ob.vmCount++   }   return  ob } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 export  class  Observer    constructor  (value) {     this .value = value     this .dep = new  Dep()     this .vmCount = 0           def(value, '__ob__' , this )     this .walk(value)   }      walk (obj) {     const  keys = Object .keys(obj)     for  (let  i = 0 ; i < keys.length; i++) {       defineReactive(obj, keys[i])     }   } } 
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 export  function  defineReactive  (obj, key, val )   const  dep = new  Dep()   const  property = Object .getOwnPropertyDescriptor(obj, key)      if  (property && property.configurable === false ) {                    return    }   if  (arguments .length === 2 ) {     val = obj[key]   }      let  childOb = observe(val)   Object .defineProperty(obj, key, {     enumerable: true ,     configurable: true ,     get : function reactiveGetter () {       const  value = val       if  (Dep.target) {                  dep.depend()                  if  (childOb) {           childOb.dep.depend()         }       }       return  value     },     set : function reactiveSetter (newVal) {       const  value = val       if  (newVal === value || (newVal !== newVal && value !== value)) {         return        }       val = newVal              childOb = observe(newVal)              dep.notify()     }   }) } 
通过defineReactive初始化Dep对象实例,接着拿到obj的属性描述符,然后对子对象进行递归调用ovserve方法,这样就保证了无论obj的结构多复杂,它的所有子属性也能变成响应式的对象,这样我们访问或修改obj中一个嵌套较深的属性,也能触发getter与setter。
依赖收集 在defineReactive中,我们实例化了一个Dep对象,Dep扮演的对象实际上就是发布-订阅模式中的订阅器,或者说是调度中心
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 let  uid = 0 export  default  class  Dep    constructor  () {     this .id = uid++     this .subs = []   }   addSub (sub) {     this .subs.push(sub)   }   removeSub (sub) {     remove(this .subs, sub)   }   depend () {               if  (Dep.target) {       Dep.target.addDep(this )     }   }   notify () {               const  subs = this .subs.slice()     for  (let  i = 0 , l = subs.length; i < l; i++) {       subs[i].update()     }   } } Dep.target = null  const  targetStack = []export  function  pushTarget  (_target )   if  (Dep.target) targetStack.push(Dep.target)   Dep.target = _target } export  function  popTarget  (  Dep.target = targetStack.pop() } 
在Dep类中,定义了一个静态属性target,是为了存储全局唯一的Watcher。因为我们在操作数据的时候,必然会涉及到数据的读取,但是我们只需要收集到需要Watcher的对象的依赖就好了,因此需要用Dep.target来判断是否要进行依赖收集,当我们运行Watcher.get()的时候,Dep.target才会被赋值。并且在同一段时间内,只能处理一个Watcher。
而Watcher所扮演的角色就是观察者,它的主要作用就是为我们需要观察的属性提供回调与收集依赖,当被观察的值发生变化时,就会受到来着dep的通知,从而触发回调函数。
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 let  uid = 0 export  default  class  Watcher    constructor  (     vm,     expOrFn,     cb   ) {     this .vm = vm     this .cb = cb     this .id = ++uid     this .deps = []     this .depIds = new  Set ()     this .expression = expOrFn.toString()     if  (typeof  expOrFn === 'function' ) {       this .getter = expOrFn     } else  {       this .getter = parsePath(expOrFn)       if  (!this .getter) {         this .getter = function  (       }     }     this .value = this .get()   }   get  () {     pushTarget(this )     const  vm = this .vm               let  value = this .getter.call(vm, vm)          popTarget()     return  value   }   addDep (dep) {     const  id = dep.id          if  (!this .depIds.has(id)) {              this .depIds.add(dep.id)       this .deps.push(dep)       dep.addSub(this )     }   }   update () {     const  value = this .get()     if  (value !== this .value || isObject(value)) {       const  oldValue = this .value       this .value = value       this .cb.call(this .vm, value, oldValue)     }   } } 
当执行new Watcher生成一个新的观察者时,就会执行Watcher类中的get方法,从而触发数据接触中的getter方法,将自身加入到Dep的subs队列中,以此完成依赖收集。
派发更新 同理,当在代码中出现this.xxx = xxx的赋值行为时,就会触发数据劫持的setter方法,此时Dep会通知subs队列中的每一个观察者都执行自身的update,以此触发new Watcher时所绑定的callback函数。
模板编译 在Vue中,render的实现也是一大核心,这里先略过,用最简单的DOM操作来完成一些简单指令的实现。举例模板语法message,然后生成对应的Watcher:
1 2 3 4 5 6 7 8 9 createWatcher: function  (node, vm, exp, dir, ext )       const  updaterFn = updater[dir + 'Updater' ]   updaterFn && updaterFn(node, this ._getVMVal(vm, exp), undefined , ext)      new  Watcher(vm, exp, (value, oldValue) => {     updaterFn && updaterFn(node, value, oldValue, ext)   }) } 
1 2 3 4 [DIRECTIVE.TEXT + 'Updater' ]: function  (node, value )    node.textContent = isDef(value) ? value : ''  } 
响应流程图