Vue源码之:从源码角度看数据绑定

参考文档:vue

https://vue-js.com/learn-vue/react

https://github.com/answershuto/learnVuegit

本文主要讲的是下图重的右边 Data 和 Watcher 之间的联系github

在vue当中,咱们都知道是经过双向数据绑定的原理来进行响应式的渲染。 那,到底具体的原理是什么呢web

固然,你们可能都会说 Object.defineProperty()方法去坚挺数据的变化。那具体是怎么监听的呢? 仅仅如此嘛?数组

Object数据类型

使Object数据变得‘可观测’

这就要借用上文提到过的Obeject.definety() 首先,咱们定义一个数据对象car浏览器

let car = {
 'brand':'BMW',  'price':3000 } 复制代码

咱们定义了这个car的品牌brand是BMW,价格price是3000。如今咱们能够经过car.brandcar.price直接读写这个car对应的属性值。可是,当这个car的属性被读取或修改时,咱们并不知情。那么应该如何作才可以让car主动告诉咱们,它的属性被修改了呢?缓存

接下来,咱们使用Object.defineProperty()改写上面的例子:闭包

let car = {}
let val = 3000 Object.defineProperty(car, 'price', {  enumerable: true,  configurable: true,  get(){  console.log('price属性被读取了')  return val  },  set(newVal){  console.log('price属性被修改了')  val = newVal  } }) 复制代码

经过Object.defineProperty()方法给car定义了一个price属性,并把这个属性的读和写分别使用get()set()进行拦截,每当该属性进行读或写操做的时候就会触发get()set()。以下图:app

能够看到,car已经能够主动告诉咱们它的属性的读写状况了,这也意味着,这个car的数据对象已是“可观测”的了。

为了把car的全部属性都变得可观测,咱们能够编写以下代码:

export class Observer {
 value: any;  dep: Dep;  vmCount: number; // number of vms that has this object as root $data   constructor (value: any) {  this.value = value  this.dep = new Dep()  this.vmCount = 0  /*  将Observer实例绑定到data的__ob__属性上面去,以前说过observe的时候会先检测是否已经有__ob__对象存放Observer实例了,def方法定义能够参考https://github.com/vuejs/vue/blob/dev/src/core/util/lang.js#L16   */  def(value, '__ob__', this)  if (Array.isArray(value)) { //这部分能够不看,数组的下面会讲到  /*  若是是数组,将修改后能够截获响应的数组方法替换掉该数组的原型中的原生方法,达到监听数组数据变化响应的效果。  这里若是当前浏览器支持__proto__属性,,则直接覆盖当前数组对象原型上的原生数组方法若是不支持该属性,则直接覆盖数组对象的原型。  */  const augment = hasProto  ? protoAugment /*直接覆盖原型的方法来修改目标对象*/  : copyAugment /*定义(覆盖)目标对象或数组的某一个方法*/  augment(value, arrayMethods, arrayKeys)   /*若是是数组则须要遍历数组的每个成员进行observe*/  this.observeArray(value)  } else {  /*若是是对象则直接walk进行绑定*/  this.walk(value)  }  }   /**  * Walk through each property and convert them into  * getter/setters. This method should only be called when  * value type is Object.  */  /*  遍历每个对象而且在它们上面绑定getter与setter。这个方法只有在value的类型是对象的时候才能被调用  */  walk (obj: Object) {  const keys = Object.keys(obj)  /*walk方法会遍历对象的每个属性进行defineReactive绑定*/  for (let i = 0; i < keys.length; i++) {  defineReactive(obj, keys[i], obj[keys[i]])  }  }   /**  * Observe a list of Array items.  */  /*对一个数组的每个成员进行observe*/  observeArray (items: Array<any>) {  for (let i = 0, l = items.length; i < l; i++) {  /*数组须要遍历每个成员进行observe*/  observe(items[i])  }  } } 复制代码
/**
 * Attempt to create an observer instance for a value,  * returns the new observer if successfully observed,  * or the existing observer if the value already has one.  */  /*  尝试建立一个Observer实例(__ob__),若是成功建立Observer实例则返回新的Observer实例,若是已有Observer实例则返回现有的Observer实例。  */ export function observe (value: any, asRootData: ?boolean): Observer | void {  if (!isObject(value)) {  return  }  let ob: Observer | void  /*这里用__ob__这个属性来判断是否已经有Observer实例,若是没有Observer实例则会新建一个Observer实例并赋值给__ob__这个属性,若是已有Observer实例则直接返回该Observer实例*/  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {  ob = value.__ob__  } else if (  /*  这里的判断是为了确保value是单纯的对象,而不是函数或者是Regexp等状况。  并且该对象在shouldConvert的时候才会进行Observer。这是一个标识位,避免重复对value进行Observer  */  observerState.shouldConvert &&  !isServerRendering() &&  (Array.isArray(value) || isPlainObject(value)) &&  Object.isExtensible(value) &&  !value._isVue  ) {  ob = new Observer(value)  }  if (asRootData && ob) {  /*若是是根数据则计数,后面Observer中的observe的asRootData非true*/  ob.vmCount++  }  return ob } 复制代码
/**
 * Define a reactive property on an Object.  */  /*为对象defineProperty上在变化时通知的属性*/ export function defineReactive (  obj: Object,  key: string,  val: any,  customSetter?: Function ) {  /*在闭包中定义一个dep对象*/  const dep = new Dep()   const property = Object.getOwnPropertyDescriptor(obj, key)  if (property && property.configurable === false) {  return  }   /*若是以前该对象已经预设了getter以及setter函数则将其取出来,新定义的getter/setter中会将其执行,保证不会覆盖以前已经定义的getter/setter。*/  // cater for pre-defined getter/setters  const getter = property && property.get  const setter = property && property.set   /*对象的子对象递归进行observe并返回子节点的Observer对象*/  let childOb = observe(val)  Object.defineProperty(obj, key, {  enumerable: true,  configurable: true,  get: function reactiveGetter () {  /*若是本来对象拥有getter方法则执行*/  const value = getter ? getter.call(obj) : val  if (Dep.target) {  /*进行依赖收集*/  dep.depend()  if (childOb) {  /*子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个depend中,一个是正在自己闭包中的depend,另外一个是子元素的depend*/  childOb.dep.depend()  }  if (Array.isArray(value)) {  /*是数组则须要对每个成员都进行依赖收集,若是数组的成员仍是数组,则递归。*/  dependArray(value)  }  }  return value  },  set: function reactiveSetter (newVal) {  /*经过getter方法获取当前值,与新值进行比较,一致则不须要执行下面的操做*/  const value = getter ? getter.call(obj) : val  /* eslint-disable no-self-compare */  if (newVal === value || (newVal !== newVal && value !== value)) {  return  }  /* eslint-enable no-self-compare */  if (process.env.NODE_ENV !== 'production' && customSetter) {  customSetter()  }  if (setter) {  /*若是本来对象拥有setter方法则执行setter*/  setter.call(obj, newVal)  } else {  val = newVal  }  /*新的值须要从新进行observe,保证数据响应式*/  childOb = observe(newVal)  /*dep对象通知全部的观察者*/  dep.notify()  }  }) } 复制代码

在上面的代码中,咱们定义了observer类,它用来将一个正常的object转换成可观测的object

而且给value新增一个__ob__属性,值为该valueObserver实例。这个操做至关于为value打上标记,表示它已经被转化成响应式了,避免重复操做

而后判断数据的类型,若是是array类型,则调用observeArray对数组的每个成员进行observe,只有object类型的数据才会调用walk将每个属性转换成getter/setter的形式来侦测变化。最后,在defineReactive中当传入的属性值仍是一个object时使用new observer(val)来递归子属性,这样咱们就能够把obj中的全部属性(包括子属性)都转换成getter/seter的形式来侦测变化。 也就是说,只要咱们将一个object传到observer中,那么这个object就会变成可观测的、响应式的object

那么如今,咱们就能够这样定义car:

let car = new Observer({
 'brand':'BMW',  'price':3000 }) 复制代码

依赖收集

什么是依赖收集

上面。咱们已经让object数据变的可观测。变的可观测之后,咱们就能知道数据何时发生了变化,那么当数据发生变化时,咱们去通知视图更新就行了。那么问题又来了,视图那么大,咱们到底该通知谁去变化?总不能一个数据变化了,把整个视图所有更新一遍吧,这样显然是不合理的。此时,你确定会想到,视图里谁用到了这个数据就更新谁呗。对!你想的没错,就是这样。

咱们给每一个数据都建一个依赖数组(由于一个数据可能被多处使用),谁依赖了这个数据(即谁用到了这个数据)咱们就把谁放入这个依赖数组中,那么当这个数据发生变化的时候,咱们就去它对应的依赖数组中,把每一个依赖都通知一遍,告诉他们:"大家依赖的数据变啦,大家该更新啦!"。这个过程就是依赖收集。

什么时候收集依赖,什么时候通知依赖更新?

所谓谁用到了这个数据,其实就是谁获取了这个数据,而可观测的数据被获取时会触发getter属性,那么咱们就能够在getter中收集这个依赖。一样,当这个数据变化时会触发setter属性,那么咱们就能够在setter中通知依赖更新。

在getter中收集依赖,在setter中通知依赖更新。

把依赖收集到哪里

咱们给每一个数据都建一个依赖数组,谁依赖了这个数据咱们就把谁放入这个依赖数组中。单单用一个数组来存放依赖的话,功能好像有点欠缺而且代码过于耦合。咱们应该将依赖数组的功能扩展一下,更好的作法是咱们应该为每个数据都创建一个依赖管理器,把这个数据全部的依赖都管理起来。OK,到这里,咱们的依赖管理器Dep类应运而生,代码以下:

// 源码位置:src/core/observer/dep.js
export default class Dep {  constructor () {  this.subs = []  }   addSub (sub) {  this.subs.push(sub)  }  // 删除一个依赖  removeSub (sub) {  remove(this.subs, sub)  }  // 添加一个依赖  depend () {  if (window.target) {  this.addSub(window.target)  }  }  // 通知全部依赖更新  notify () {  const subs = this.subs.slice()  for (let i = 0, l = subs.length; i < l; i++) {  subs[i].update()  }  } }  /**  * Remove an item from an array  */ export function remove (arr, item) {  if (arr.length) {  const index = arr.indexOf(item)  if (index > -1) {  return arr.splice(index, 1)  }  } } 复制代码

在上面的依赖管理器Dep类中,咱们先初始化了一个subs数组,用来存放依赖,而且定义了几个实例方法用来对依赖进行添加,删除,通知等操做。

有了依赖管理器后,咱们就能够在getter中收集依赖,在setter中通知依赖更新了,代码以下

/**
 * Define a reactive property on an Object.  */  /*为对象defineProperty上在变化时通知的属性*/ export function defineReactive (  obj: Object,  key: string,  val: any,  customSetter?: Function ) {  /*在闭包中定义一个dep对象*/  const dep = new Dep()   const property = Object.getOwnPropertyDescriptor(obj, key)  if (property && property.configurable === false) {  return  }   /*若是以前该对象已经预设了getter以及setter函数则将其取出来,新定义的getter/setter中会将其执行,保证不会覆盖以前已经定义的getter/setter。*/  // cater for pre-defined getter/setters  const getter = property && property.get  const setter = property && property.set   /*对象的子对象递归进行observe并返回子节点的Observer对象*/  let childOb = observe(val)  Object.defineProperty(obj, key, {  enumerable: true,  configurable: true,  get: function reactiveGetter () {  /*若是本来对象拥有getter方法则执行*/  const value = getter ? getter.call(obj) : val  if (Dep.target) {  /*进行依赖收集*/  dep.depend()  if (childOb) {  /*子对象进行依赖收集,其实就是将同一个watcher观察者实例放进了两个depend中,一个是正在自己闭包中的depend,另外一个是子元素的depend*/  childOb.dep.depend()  }  if (Array.isArray(value)) {  /*是数组则须要对每个成员都进行依赖收集,若是数组的成员仍是数组,则递归。*/  dependArray(value)  }  }  return value  },  set: function reactiveSetter (newVal) {  /*经过getter方法获取当前值,与新值进行比较,一致则不须要执行下面的操做*/  const value = getter ? getter.call(obj) : val  /* eslint-disable no-self-compare */  if (newVal === value || (newVal !== newVal && value !== value)) {  return  }  /* eslint-enable no-self-compare */  if (process.env.NODE_ENV !== 'production' && customSetter) {  customSetter()  }  if (setter) {  /*若是本来对象拥有setter方法则执行setter*/  setter.call(obj, newVal)  } else {  val = newVal  }  /*新的值须要从新进行observe,保证数据响应式*/  childOb = observe(newVal)  /*dep对象通知全部的观察者*/  dep.notify()  }  }) } 复制代码

依赖究竟是谁

其实在Vue中还实现了一个叫作Watcher的类,而Watcher类的实例就是咱们上面所说的那个"谁"。换句话说就是:谁用到了数据,谁就是依赖,咱们就为谁建立一个Watcher实例。在以后数据变化时,咱们不直接去通知依赖更新,而是通知依赖对应的Watch实例,由Watcher实例去通知真正的视图。

Watcher类的具体实现以下:

export default class Watcher {
 constructor (vm,expOrFn,cb) {  this.vm = vm;  this.cb = cb;  this.getter = parsePath(expOrFn)  this.value = this.get()  }  get () {  window.target = this;  const vm = this.vm  let value = this.getter.call(vm, vm)  window.target = undefined;  return value  }  update () {  const oldValue = this.value  this.value = this.get()  this.cb.call(this.vm, this.value, oldValue)  } }  /**  * Parse simple path.  * 把一个形如'data.a.b.c'的字符串路径所表示的值,从真实的data对象中取出来  * 例如:  * data = {a:{b:{c:2}}}  * parsePath('a.b.c')(data) // 2  */ const bailRE = /[^\w.$]/ export function parsePath (path) {  if (bailRE.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  } } 复制代码

谁用到了数据,谁就是依赖,咱们就为谁建立一个Watcher实例,在建立Watcher实例的过程当中会自动的把本身添加到这个数据对应的依赖管理器中,之后这个Watcher实例就表明这个依赖,当数据变化时,咱们就通知Watcher实例,由Watcher实例再去通知真正的依赖。

那么,在建立Watcher实例的过程当中它是如何的把本身添加到这个数据对应的依赖管理器中呢?

下面咱们分析Watcher类的代码实现逻辑:

当实例化Watcher类时,会先执行其构造函数; 在构造函数中调用了this.get()实例方法; 在get()方法中,首先通过window.target = this把实例自身赋给了全局的一个惟一对象window.target上,而后经过let value = this.getter.call(vm, vm)获取一下被依赖的数据,获取被依赖数据的目的是触发该数据上面的getter,上文咱们说过,在getter里会调用dep.depend()收集依赖,而在dep.depend()中取到挂载window.target上的值并将其存入依赖数组中,在get()方法最后将window.target释放掉。

而当数据变化时,会触发数据的setter,在setter中调用了dep.notify()方法,在dep.notify()方法中,遍历全部依赖(即watcher实例),执行依赖的update()方法,也就是Watcher类中的update()实例方法,在update()方法中调用数据变化的更新回调函数,从而更新视图。

简单总结一下就是:Watcher先把本身设置到全局惟一的指定位置(window.target),而后读取数据。由于读取了数据,因此会触发这个数据的getter。接着,在getter中就会从全局惟一的那个位置读取当前正在读取数据的Watcher,并把这个watcher收集到Dep中去。收集好以后,当数据发生变化时,会向Dep中的每一个Watcher发送通知。经过这样的方式,Watcher能够主动去订阅任意一个数据的变化。为了便于理解,咱们画出了其关系流程图,以下图:

以上,就完全完成了对Object数据的侦测,依赖收集,依赖的更新等全部操做。

不足之处

虽然咱们经过Object.defineProperty方法实现了对object数据的可观测,可是这个方法仅仅只能观测到object数据的取值及设置值,当咱们向object数据里添加一对新的key/value或删除一对已有的key/value时,它是没法观测到的,致使当咱们对object数据添加或删除值时,没法通知依赖,没法驱动视图进行响应式更新。

固然,Vue也注意到了这一点,为了解决这一问题,Vue增长了两个全局API:Vue.set和Vue.delete,这两个API的实现原理将会在后面学习全局API的时候说到。

总结

首先,咱们经过Object.defineProperty方法实现了对object数据的可观测,而且封装了Observer类,让咱们可以方便的把object数据中的全部属性(包括子属性)都转换成getter/seter的形式来侦测变化。

接着,咱们学习了什么是依赖收集?而且知道了在getter中收集依赖,在setter中通知依赖更新,以及封装了依赖管理器Dep,用于存储收集到的依赖。

最后,咱们为每个依赖都建立了一个Watcher实例,当数据发生变化时,通知Watcher实例,由Watcher实例去作真实的更新操做。

其整个流程大体以下:

Data经过observer转换成了getter/setter的形式来追踪变化。 当外界经过Watcher读取数据时,会触发getter从而将Watcher添加到依赖中。 当数据发生了变化时,会触发setter,从而向Dep中的依赖(即Watcher)发送通知。 Watcher接收到通知后,会向外界发送通知,变化通知到外界后可能会触发视图更新,也有可能触发用户的某个回调函数等。

Array 数据类型

如何侦测

为何Object数据和Array型数据会有两种不一样的变化侦测方式?

这是由于对于Object数据咱们使用的是JS提供的对象原型上的方法Object.defineProperty,而这个方法是对象原型上的,因此Array没法使用这个方法,因此咱们须要对Array型数据设计一套另外的变化侦测机制。

万变不离其宗,虽然对Array型数据设计了新的变化侦测机制,可是其根本思路仍是不变的。那就是:仍是在获取数据时收集依赖,数据变化时通知依赖更新。

在哪里收集依赖

回想一下日常在开发的时候,在组件的data中是否是都这么写的:

data(){
 return {  arr:[1,2,3]  } } 复制代码

arr这个数据始终都存在于一个object数据对象中,并且咱们也说了,谁用到了数据谁就是依赖,那么要用到arr这个数据,是否是得先从object数据对象中获取一下arr数据,而从object数据对象中获取arr数据天然就会触发arrgetter,因此咱们就能够在getter中收集依赖。

总结一句话就是:Array型数据仍是在getter中收集依赖。

使Array型数据可观测

Array型数据发生变化,那必然是操做了Array,而JS中提供的操做数组的方法就那么几种,咱们能够把这些方法都重写一遍,在不改变原有功能的前提下,咱们为其新增一些其余功能,例以下面这个例子:

let arr = [1,2,3]
arr.push(4) Array.prototype.newPush = function(val){  console.log('arr被修改了')  this.push(val) } arr.newPush(4) 复制代码

数组方法拦截器

在Vue中建立了一个数组方法拦截器,它拦截在数组实例与Array.prototype之间,在拦截器内重写了操做数组的一些方法,当数组实例使用操做数组方法时,其实使用的是拦截器中重写的方法,而再也不使用Array.prototype上的原生方法。以下图所示:

/*取得原生数组的原型*/
const arrayProto = Array.prototype /*建立一个新的数组对象,修改该对象上的数组的七个方法,防止污染原生数组方法*/ export const arrayMethods = Object.create(arrayProto) /**  * Intercept mutating methods and emit events  */  /*这里重写了数组的这些方法,在保证不污染原生数组原型的状况下重写数组的这些方法,截获数组的成员发生的变化,执行原生数组操做的同时dep通知关联的全部观察者进行响应式处理*/ ;[  'push',  'pop',  'shift',  'unshift',  'splice',  'sort',  'reverse' ] .forEach(function (method) {  // cache original method  /*将数组的原生方法缓存起来,后面要调用*/  const original = arrayProto[method]  def(arrayMethods, method, function mutator () {  // avoid leaking arguments:  // http://jsperf.com/closure-with-arguments  let i = arguments.length  const args = new Array(i)  while (i--) {  args[i] = arguments[i]  }  /*调用原生的数组方法*/  const result = original.apply(this, args)   /*数组新插入的元素须要从新进行observe才能响应式*/  const ob = this.__ob__  let inserted  switch (method) {  case 'push':  inserted = args  break  case 'unshift':  inserted = args  break  case 'splice':  inserted = args.slice(2)  break  }  if (inserted) ob.observeArray(inserted)   // notify change  /*dep通知全部注册的观察者进行响应式处理*/  ob.dep.notify()  return result  }) }) 复制代码

使用拦截器

在上一小节的图中,咱们把拦截器作好还不够,还要把它挂载到数组实例与Array.prototype之间,这样拦截器才可以生效。

其实挂载不难,咱们只需把数据的__proto__属性设置为拦截器arrayMethods便可,源码实现以下

if (Array.isArray(value)) {
 /*  若是是数组,将修改后能够截获响应的数组方法替换掉该数组的原型中的原生方法,达到监听数组数据变化响应的效果。  这里若是当前浏览器支持__proto__属性,,则直接覆盖当前数组对象原型上的原生数组方法若是不支持该属性,则直接覆盖数组对象的原型。  */  const augment = hasProto  ? protoAugment /*直接覆盖原型的方法来修改目标对象*/  : copyAugment /*定义(覆盖)目标对象或数组的某一个方法*/  augment(value, arrayMethods, arrayKeys)   /*若是是数组则须要遍历数组的每个成员进行observe*/  this.observeArray(value)  } 复制代码
/**
 * Augment an target Object or Array by intercepting  * the prototype chain using __proto__  */  /*直接覆盖原型的方法来修改目标对象或数组*/ function protoAugment (target, src: Object) {  /* eslint-disable no-proto */  target.__proto__ = src  /* eslint-enable no-proto */ }  /**  * Augment an target Object or Array by defining  * hidden properties.  */ /* istanbul ignore next */ /*定义(覆盖)目标对象或数组的某一个方法*/ function copyAugment (target: Object, src: Object, keys: Array<string>) {  for (let i = 0, l = keys.length; i < l; i++) {  const key = keys[i]  def(target, key, src[key])  } } 复制代码

上面代码中首先判断了浏览器是否支持__proto__,若是支持,则调用protoAugment函数把value.__proto__ = arrayMethods;若是不支持,则调用copyAugment函数把拦截器中重写的7个方法循环加入到value上。

数组新增元素侦测

咱们知道,能够向数组内新增元素的方法有3个,分别是:pushunshiftsplice。咱们只需对这3中方法分别处理,拿到新增的元素,再将其转化便可

若是是pushunshift方法,那么传入参数就是新增的元素;若是是splice方法,那么传入参数列表中下标为2的就是新增的元素,拿到新增的元素后,就能够调用observe函数将新增的元素转化成响应式的了。

数组如何收集依赖

数组的依赖也在getter中收集,那么在getter中到底该如何收集呢?这里有一个须要注意的点,那就是依赖管理器定义在Observer类中,而咱们须要在getter中收集依赖,也就是说咱们必须在getter中可以访问到Observer类中的依赖管理器,才能把依赖存进去。源码是这么作的:

function defineReactive (obj,key,val) {
 let childOb = observe(val)  Object.defineProperty(obj, key, {  enumerable: true,  configurable: true,  get(){  if (childOb) {  childOb.dep.depend()  }  return val;  },  set(newVal){  if(val === newVal){  return  }  val = newVal;  dep.notify() // 在setter中通知依赖更新  }  }) }  /**  * Attempt to create an observer instance for a value,  * returns the new observer if successfully observed,  * or the existing observer if the value already has one.  * 尝试为value建立一个0bserver实例,若是建立成功,直接返回新建立的Observer实例。  * 若是 Value 已经存在一个Observer实例,则直接返回它  */ export function observe (value, asRootData){  if (!isObject(value) || value instanceof VNode) {  return  }  let ob  if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {  ob = value.__ob__  } else {  ob = new Observer(value)  }  return ob } 复制代码

在上面代码中,咱们首先经过observe函数为被获取的数据arr尝试建立一个Observer实例,在observe函数内部,先判断当前传入的数据上是否有__ob__属性,由于在上篇文章中说了,若是数据有__ob__属性,表示它已经被转化成响应式的了,若是没有则表示该数据还不是响应式的,那么就调用new Observer(value)将其转化成响应式的,并把数据对应的Observer实例返回。

而在defineReactive函数中,首先获取数据对应的Observer实例childOb,而后在getter中调用Observer实例上依赖管理器,从而将依赖收集起来。

深度侦测

let arr = [
 {  name:'NLRX' age:'18'  } ] 复制代码

数组中包含了一个对象,若是该对象的某个属性发生了变化也应该被侦测到,这就是深度侦测。

if (Array.isArray(value)) {  
 /*  若是是数组,将修改后能够截获响应的数组方法替换掉该数组的原型中的原生方法,达到监听数组数据变化响应的效果。  这里若是当前浏览器支持__proto__属性,,则直接覆盖当前数组对象原型上的原生数组方法若是不支持该属性,则直接覆盖数组对象的原型。  */  const augment = hasProto  ? protoAugment /*直接覆盖原型的方法来修改目标对象*/  : copyAugment /*定义(覆盖)目标对象或数组的某一个方法*/  augment(value, arrayMethods, arrayKeys)   /*若是是数组则须要遍历数组的每个成员进行observe*/  this.observeArray(value)  } else {  /*若是是对象则直接walk进行绑定*/  this.walk(value)  } 复制代码

如何通知依赖

咱们应该在拦截器里通知依赖,要想通知依赖,首先要能访问到依赖。要访问到依赖也不难,由于咱们只要能访问到被转化成响应式的数据value便可,由于vaule上的__ob__就是其对应的Observer类实例,有了Observer类实例咱们就能访问到它上面的依赖管理器,而后只需调用依赖管理器的dep.notify()方法,让它去通知依赖更新便可。源码以下:

def(arrayMethods, method, function mutator () {
 // avoid leaking arguments:  // http://jsperf.com/closure-with-arguments  let i = arguments.length  const args = new Array(i)  while (i--) {  args[i] = arguments[i]  }  /*调用原生的数组方法*/  const result = original.apply(this, args)   /*数组新插入的元素须要从新进行observe才能响应式*/  const ob = this.__ob__  let inserted  switch (method) {  case 'push':  inserted = args  break  case 'unshift':  inserted = args  break  case 'splice':  inserted = args.slice(2)  break  }  if (inserted) ob.observeArray(inserted)   // notify change  /*dep通知全部注册的观察者进行响应式处理*/  ob.dep.notify()  return result  }) 复制代码

不足之处

前文中咱们说过,对于数组变化侦测是经过拦截器实现的,也就是说只要是经过数组原型上的方法对数组进行操做就均可以侦测到,可是别忘了,咱们在平常开发中,还能够经过数组的下标来操做数据,以下:

let arr = [1,2,3]
arr[0] = 5; // 经过数组下标修改数组中的数据 arr.length = 0 // 经过修改数组长度清空数组 复制代码

而使用上述例子中的操做方式来修改数组是没法侦测到的。 一样,Vue也注意到了这个问题, 为了解决这一问题,Vue增长了两个全局API:Vue.setVue.delete,这两个API的实现原理将会在后面学习全局API的时候说到。

总结

首先咱们分析了对于Array型数据也在getter中进行依赖收集;其次咱们发现,当数组数据被访问时咱们垂手可得能够知道,可是被修改时咱们却很难知道,为了解决这一问题,咱们建立了数组方法拦截器,从而成功的将数组数据变的可观测。接着咱们对数组的依赖收集及数据变化如何通知依赖进行了深刻分析;最后咱们发现Vue不但对数组自身进行了变化侦测,还对数组中的每个元素以及新增的元素都进行了变化侦测,咱们也分析了其实现原理。

本文使用 mdnice 排版

相关文章
相关标签/搜索