vue2-数据响应式原理(一)

响应式用了obejct.defineProperty属性可以看看前面的文章:
https://www.jianshu.com/p/7f0ff748eb76

首先我们先不看Dep类和Watcher类。先把简单的解决。再来添加;

开始

首先先说大体的思路:

  1. 入口 observe 会先查看 对象有没有 __ob__ 的属性,如果没有的话,并且 实例化Observer方法(方法会把__ob__属性挂载到对象上)。
  2. Observer 会遍历下一层属性, 逐个调用 defineReactive方法, 转换属性为响应式。 Observer 会把__ob__ 属性 挂载到对象上。 (如果是属性对应的值是数组,会把数组的方法改写,以便使用数组的push,pop等方法也会触发响应式)
  3. defineReactive方法, 会把对象 上的属性转换为 响应式。 如果属性 的值(或者 修改后的值) 又是 一个对象的话,又会调用 observe方法 重新 走一遍1,2,3 把对应值的属性转换为响应式;

首先比如我们有个对象

var obj = {
  a: {
    m: {
      n: 999
    }
  },
  b: 4,
  g: [1,2,3,4]
}
observe(obj)

要让这个对象响应式,那么我们需要一个 函数 来转换它。
这个时候会引入一个 observe函数(判断是否有__ob__,没有就调用 Observer)

// observe.js
import Observer from './Observer'

// 入口文件 实现动态监听  
// 创建observe 函数 判断是否有__ob__,没有就调用 Observer
export default function observe (value) {
  //如果 value 不是对象, 直接返回(只能为对象服务)
  if (typeof value !== 'object') return;
  // 定义 ob
  var ob;
  // __ob__ 存储有 Observer类 的实例
  if (typeof value.__ob__ !== 'undefined') {
    ob = value.__ob__;
  } else {
    ob = new Observer(value);
  }

  return ob;
}

observer 方法里面要注意 判断 对象是不是数组,对象我们改变 值的时候可以触发 响应式 ,但是对于数组的push,pop,shift等方法 需要单独处理,才能实现响应式

// observer.js
import { def } from './util.js'
import defineReactive from './defineReactive.js'
import { arrayMethods } from './array.js'
import observe from './Observe.js'


// 把对象 转换 成 每个层级的属性 都是响应式的对象(可以被侦测); 比如: obj:{a:{b:{c: 5}}}
export default class Observer {
  constructor(value) {
    // 给value(对象) 添加了 __ob__ 的属性,值是 这次new 的实例(构造函数中的this 表示的是实例),并且不可枚举
    def(value, '__ob__', this, false)
    
    // 检查value 是不是 数组
    if (Array.isArray(value)) {
      // 如果value是数组, 把value的原型,指向 arrayMethods(push等7个方法 被 改写过)
      Object.setPrototypeOf(value, arrayMethods) // 或者用 value.__proto__ = arrayMethods
      // 数组 实现响应式
      this.observeArray(value)

    } else {
      // 对象 直接 调用walk 方法  转换 为响应式
      this.walk(value)
    }
  }
  // 遍历 value对象上的每一个 属性,并且 调用 defineReactive方法 转换为响应式
  walk (value) {
    for (let k in value) {
      defineReactive(value, k)
    }
  }

  // 数组的特殊遍历
  observeArray (arr) {
    for (let i = 0, l = arr.length; i < l; i++) { 
      // 逐项进行observe
      observe(arr[i])
    }

  }
}
//util.js
// def 方法定义对象  并且属性是否可以被枚举
export const def = function (obj, key, value, enumerable) {
  Object.defineProperty(obj, key, {
    value,
    enumerable, // 是否可枚举
    writable: true, //可写
    configurable: true, //可删除
  })
}
// defineReactive.js

import observe from './Observe.js'


// 包裹 Object.defineProperty, 把 对象上的属性  转换为响应式
// (为什么要包裹? 因为:如果key为常量的时,get返回值永远是一个值。包裹之后才能 修改); 
export default function defineReactive (data, key, val) {
  // 如果值传入两个参数 ,那么 val 就是  对象对应的值
  if(arguments.length===2){
    val = data[key]
  }

  //如果val 是对象,对象上的属性也要变化动态的。如果不是对象(常数、undefined等),observe函数里面不会处理
  // 子元素也要 observe; (observe,observer,defineReactive三个函数相互调用,形成了类似递归的效果)
  let chilOb = observe(val)

  Object.defineProperty(data, key, {
    enumerable: true, //可以被枚举
    configurable: true, // 该属性的描述符能被改变,能被删除
    // 访问的时候触发 get函数
    get () {
      // console.log('触发getter');
      // 只要调用了observe 监听对象后,有获取对象上值的时候 就会触发get方法
      return val
    },
    // 修改的时候触发 set 函数
    set (newValue) {
      // console.log('触发setter', newValue);
      if (val === newValue) return;
      val = newValue

      // 当设置 新值的时候也要调用  observe 方法把 新值(如果是对象)转换为响应式
      chilOb = observe(newValue)
    }
  })
}

// array.js

// 重写 数组的七个方法 以便实现响应式(使用方法的时候触发依赖), 把重写后的 方法暴露出去
// 为什么要有这个方法 。改变对象的值,可以触发依赖更新。但是 数组的 push,pop等方法不会触发依赖更新。所有需要重写数组的七个方法 以实现 使用push,pop等方法时触发依赖更新

import { def } from './util.js'

// 得到 Array.prototype
const arrayPrototype = Array.prototype

// 需要被改写的 数组方法
const methodsNeedChange = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']

// 以 Array.prototype 为原型创建 arrayMethods 对象
export const arrayMethods = Object.create(arrayPrototype)

methodsNeedChange.forEach(methodName => {
  // 备份原来的方法, 方法原来的功能需要保留
  const original = arrayPrototype[methodName]

  // 定义新的方法(方法是对象,所有可以调用前面封装的def方法) 
  def(arrayMethods, methodName, function () {

    // 恢复原来的功能,执行数组方法
    const result = original.apply(this, arguments)

    // push, unshift, splice 会插入新值。 新值需要调用observe方法也 转换为 响应式
    let inserted = null

    // 获得 数组身上 的__ob__
    const ob = this.__ob__;

    // arguments 是类数组 ,没有slice方法 ,把arguments 转换为数组
    const ars = [...arguments]

    switch (methodName) {
      case 'push':
      case 'unshift':
        inserted = arguments;
        break;
      case 'splice':
        // splice (位置,删除几项, 插入的第1个值,插入的第2个值,插入的第3个值。。。)
        inserted = ars.slice(2)
        break;
    }

    // 判断有没有inserted, 有新值 也变为响应式
    if (inserted) {
      ob.observeArray(inserted)
    }
    console.log('触发array.js_啦啦啦啦啦啦');
    // pop,shift 会有返回值
    return result

  }, false)
})

Dep 和 Watcher

上面代码里面的注释已经很详细了。如果看懂了。那接下来我们使用Dep 和 Watcher 是数据变化后真正可以更新页面上的数据;

  • Dep 类 是干什么的呢 ? Dep 类收集 依赖 然后 触发依赖更新 (其中每一个依赖都是Watcher实例)
  • Watcher 是 监听对象 的表示式 然后改变的时候 调用回调,这个时候我们就能更新页面的数据了

这里有几个点

  • Dep 类 把所有依赖收集到一起, 专门用来管理依赖的类。 每一个Observer的实例 ,成员都有一个Dep的实例。
  • Watcher是一个中介, 数据发生改变的时候, 通过Watcher中转,通知组件。
  • 在getter 中收集依赖, 在setter中 触发依赖 (其中每一个依赖 其实就是 watcher)
  • 代码的巧妙之处是: watcher 把自己设置到一个全局位置(Dep类的身上,Dep类只有一个,当然也可以设置到window上只要唯一就行),当读取数据是,触发这个数据的 getter,getter中 获取正在读取数据的wathcer(依赖),把这个watcher 收集到Dep中
  • Dep 使用发布订阅模式,只有有数据变化,就把依赖列表(收集了很多依赖),循环通知一遍。

比如下面:

// index.js
import observe from './Observe.js'
import Watcher from './Watcher.js'
var obj = {
  a: {
    m: {
      n: 999
    }
  },
  b: 4,
  g: [1,2,3,4]
}



observe(obj)
// watcher obj的时候,obj已经被observe观察了。
// 此时 在watcher中要获取 obj.a.m.n的值的时候,就会触发defineReactive 中 getter方法。
// getter方法中就会收集当前这个watcher的实例到依赖列表中
new Watcher(obj, 'a.m.n', (newValue, oldValue)=>{
  console.log('**************');
  // 去更新页面上 {{a.m.n}}
})
// 由于a.m.n的实例已经被收集到依赖列表中了
// 此时改变 a.m.n是就会 调用 上面watcher的回调函数了。
obj.a.m.n = 198

开始:
Dep类

// dep.js
var uid = 0;
// Dep 类收集 依赖 然后 触发依赖更新 (其中每一个依赖都是Watcher实例)
export default class Dep {
  constructor() {
    this.id = uid++;
    // 存储自己的订阅者(收集的watcher实例)。
    this.subs = []
  }

  // 添加订阅
  addSub (sub) {
    this.subs.push(sub)
  }

  // 添加依赖
  depend(){
    // Dep.target 自定义的一个全局唯一位置。也可以用window.__myOnly__随便啥都行;
    // Dep.target 上是正在读取数据的watcher, 之后会重置为null;
    console.log('depend', Dep.target);
    if(Dep.target){
      this.addSub(Dep.target)
    }
  }

  // 触发依赖 通知更新
  notify () {
    // 浅克隆
    const subs = this.subs.slice();
    // 遍历
    for (let i = 0, l = subs.length; i < l; i++) {
      // 每一个 watcher 调用 update更新方法
      subs[i].update()
    }
  }

}

Watcher类

// watcher.js
import Dep from './Dep'

// 监听对象 的表示式  触发回调
// vm.$watch('a.b.c.d', ()=>{})   a.b.c.d是下面的表达式
var uid = 0;
export default class Watcher {
  constructor(target, expressiong, callback) {
    this.id = uid++;
    // 需要监听的对象 
    this.target = target;
    // 获取 表达式 解析后的函数
    this.getter = parsePath(expressiong)
    // 回调
    this.callback = callback;
    // 获取 Watcher 实例的 值
    this.value = this.get()
  }
  // 更新
  update () {
    this.getAndInvoke(this.callback)
  }
  // 得到并唤起
  getAndInvoke (cb) {
    const value = this.get()

    // 如果更新后 到的值不是 当前的值 或者 是一个对象  。 那么更新value 并执行回调 
    if (value !== this.value || typeof value == 'object') {
      const oldValue = this.value;
      this.value = value
      cb.call(this, value, oldValue)
    }
  }
  // 获取
  get () {
    console.log('进入wathcer');
    // 进入依赖收集 阶段 (把watcher 设置全局唯一位置,以便Dep收集 当前的watcher实例)
    Dep.target = this;

    const obj = this.target

    // 获取 对象中表示式 对应的值
    let value;
    // 只要能找,就一直找
    try {
      // obj对象 在watcher 实例之前 就已经observe了。 (此时要获取值,就会触发 getter方法)
      // 所以这个时候 调用this.getter(obj) 会触发 defineReactive 的getter方法 。
      // getter方法中 判断 如果 Dep.target存在(此时存在了,就是这个Watcher实例),就会收集 watcher实例到 dep中
      value = this.getter(obj)
    } finally {
      // get结束后 ,说明读结束了。要把全局位置 让出来
      Dep.target = null;
    }
    console.log('进入wathcer-end', Dep.target);
    return value
  }
}

// 把a.b.c.d 处理 返回一个函数  调用返回的函数 可以获取 函数的值 
// 比如   var fn = parsePath('a.b.c.d')  ;  fn({a:{b:{c:{d:88}}}}) 获取 88
function parsePath (str) {
  var list = str.split('.')
  return (obj) => {
    if (!obj) return;
    for (let i = 0; i < list.length; i++) {
      obj = obj[list[i]]
    }
    return obj
  }
}

  1. 挂载dep ; 每一个observer的实例都会有一个dep;这个dep是用来收集 数组 的依赖的
// observer.js
import Dep from './Dep.js'

export default class Observer {
  constructor(value) {
    // *new add* 每一个Observer的实例身上,都有一个Dep(也就是说__ob__上会有 dep). 目的是挂载上dep
    //  *new add* 每一个 Observer 实例上会有 dep 用来收集  数组 的依赖 
    //  *new add* 在对对象调用 defineReactive 实现依赖收集时,如果判断 对象上一项是 数组,就会 收集依赖 到 这个 dep上
    this.dep = new Dep()
}
  1. 然后是收集依赖 和触发依赖
    对象中 收集和触发
// defineReactive.js

import observe from './Observe.js'
import Dep from './Dep.js'
import { dependArray } from './array.js'

export default function defineReactive (data, key, val) {
  // *new add* 获取Dep 的实例 便于收集和触发依赖 
  // *new add* 每一个 对象监听的 属性,都需要一个 dep 收集依赖
  // *new add*  数组的依赖 需要到 Observer上的一个dep上 ,下面的
  const dep = new Dep()
 
  if(arguments.length===2){
    val = data[key]
  }

  // *new add* 如果val 是对象,对象上的属性也要变化动态的。如果不是对象(常数、undefined等),observe函数里面不会处理
  // *new add* 子元素也要 observe; (observe,observer,defineReactive三个函数相互调用,形成了类似递归的效果)
  // *new add* observe 返回 Observer的实例, 实例上有 收集数组 的dep 
  let chilOb = observe(val)

  Object.defineProperty(data, key, {
    enumerable: true, 
    configurable: true,
    get () {
      // *new add*  如果处于依赖收集阶段, 那么就收集依赖
     // Dep.target 是全局的一个唯一值,是正在被watcher的实例
      if(Dep.target) {
        dep.depend()
        // 如果子元素 存在的话, 也要收集依赖
        if (chilOb) {
          chilOb.dep.depend() // *new add* 数组收集当前渲染的watcher
          dependArray(val) // *new add* 收集儿子的依赖
        }
      }
      return val
    },
    // 修改的时候触发 set 函数
    set (newValue) {
      if (val === newValue) return;
      val = newValue

      chilOb = observe(newValue)

      // *new add*  这里是对象 改变 触发依赖 通知 dep
      dep.notify()
    }
  })
}

然后 数组中我们调用push,pop等方法是也要触发依赖

// array.js
methodsNeedChange.forEach(methodName => {

  const original = arrayPrototype[methodName]

  def(arrayMethods, methodName, function () {

    const result = original.apply(this, arguments)

    let inserted = null

    const ob = this.__ob__;

    const ars = [...arguments]

    switch (methodName) {
      case 'push':
      case 'unshift':
        inserted = arguments;
        break;
      case 'splice':
        inserted = ars.slice(2)
        break;
    }

    if (inserted) {
      ob.observeArray(inserted)
    }

    // *new add* 这里是 数组变化  触发依赖
   // 上面给每一个observer都挂载了dep,那么`__ob__`上也会有dep
    ob.dep.notify()

    return result

  }, false)
})

// *new add* dependArray() 收集儿子的依赖
export function dependArray(value) {
  for (let i = 0; i < value.length; i++) {
      let currentItem = value[i]
      currentItem.__ob__&& currentItem.__ob__.dep.depend()
      if(Array.isArray(currentItem)){
          dependArray(currentItem) // 递归收集依赖
      }
  }
}

这个加上Dep 和 Watcher 之后是有点绕,需要慢慢想一想;

// new add 每一个 Observer 实例上会有 dep 用来收集 数组 的依赖
// new add 在对对象调用 defineReactive 实现依赖收集时,如果 对象上 某一项是 数组或对象,就会 收集依赖 到 这个 dep上
// 以便于 没有触发 defineReactive 的 set 方法中 dep.notify(),我们可以 使用 Observer 实例上的 ob 上的 另一个 dep 触发 notify
// Observer实例上的 dep 和 Observer实例上(对象)上 某一项 obj[key] 中 key 的 dep 收集的是同一个依赖。所有 修改 obj[key] 或者 调用 数组的push等等触发的 __ob__.dep.notify 都会 通知依赖更新

比如:
obj1={list1: [1,2,3]} =>
Observe(obj1)=>
生成Observer实例 obj1_Observer = new Observer(obj1)=>
实例 生成一个Dep实例 dep1 => obj1_Observer__ob__dep1
然后 对 obj1.list1 调用 defineReactive(obj1, list1)
会生成一个新的Dep 实例 dep2 (监听 obj1.list1的修改)
同时 obj1.list1对应的 [1,2,3] 是一个数组或对象
又会调用 observe([1,2,3])
返回一个新的 Observer实例observer2
observer2 上又会产生一个 新的Dep实例 dep3=> observer2__ob__dep3
当 在 get 访问中 访问 obj1.list1时, 会收集 dep2的依赖,并且收集dep3。(由于访问都是又Watcher触发的,所有收集的都是相同的依赖)
当修改对象 的具体 数值的时候, 会触发 defineReactive中set 方法, 然后 set 方法 可以访问 到 dep2, 所有调用dep2.notify触发依赖更新
由于数组的更改比如push,shift,splice等无法触发 defineReactive中的set 方法 。我们 修改 了 Observer实例 observer2 中的 数组 方法。
当调用数组修改后 的方法时,我们 就调用 Observer实例 observer2 上的 observer2__ob__dep3=> dep3.notify 触发依赖 更新
总结来说。我们对 对象 某一项 转换为响应式的 时候,不仅仅 收集 可以通过defeineProperty修改某一项的 依赖; 还留一个口, 用来无法通过 defeineProperty监听的数据,我们通过其他方法 收集依赖
比如上面我们通过 修改 obj1.list1 = 2 可以触发 dep2 更新依赖
通过 obj1.list1.push(3) 可以触发 dep3 更新依赖
而依赖都是同一个 Watcher 。所有都是处理 同一个事件,比如更新模板

下面是所有的代码:


image.png

index.js

import defineReactive from './defineReactive.js'
import observe from './Observe.js'
import Watcher from './Watcher.js'
var obj = {
  // a: {
  //   m: {
  //     n: 999
  //   }
  // },
  // b: 4,
  g: [1,2,3,4]
}



observe(obj)
new Watcher(obj, 'g', (newVal, oldVal)=>{
  console.log('watch', newVal, oldVal);
})
obj.g.push(5)
// new Watcher(obj, 'a.m.n', (newValue, oldValue)=>{
//   console.log('**************', newValue, oldValue);
//   // 去更新页面上 {{a.m.n}}
// })
// obj.a.m.n = 198

// console.log('aaaaaaaaaaaaaa', a);
// obj.a.m.n = 44
// // obj.g.splice(2,1,66,77,88)
// obj.g.push({"test": 6})
// console.log('obj', obj);


// 先不说依赖 和 watch  Dep类 和 watcher类

//1. 入口 observe 会先查看 对象有没有 __ob__ 的属性,如果没有的话,并且 实例化Observer方法(方法会把__ob__属性挂载到对象上)。

//2. Observer 会遍历下一层属性, 逐个调用 defineReactive方法, 转换属性为响应式。  Observer 会把 __ob__ 属性 挂载到对象上。  (如果是属性对应的值是数组,会把数组的方法改写,以便掉数组的push,pop等方法也会触发响应式)

//3. defineReactive方法, 会把对象 上的属性转换为 响应式。 如果属性 的值(或者 修改后的值) 又是 一个对象的话,又会调用 observe方法 重新 走一遍1,2,3 把对应值的属性转换为响应式。 并且 属性值是对象 会返回 Observer的实例,实例上有一个dep,就可以用来收集数组依赖


// Dep 类 把所有依赖收集到一起, 专门用来管理依赖的类。  每一个Observer的实例 ,成员都有一个Dep的实例, 用来收集数组的依赖
// Watcher是一个中介, 数据发生改变的时候, 通过Watcher中转,通知组件。
// 在getter 中收集依赖, 在setter中 触发依赖 (其中每一个依赖 其实就是 watcher)
// 代码的巧妙之处是: watcher 把自己设置到一个全局位置(Dep类的身上,Dep类只有一个,当然也可以设置到window上只要唯一就行),当读取数据是,触发这个数据的 getter,getter中 获取正在读取数据的wathcer(依赖),把这个watcher手机到Dep中
// Dep 使用发布订阅模式,只有有数据变化,就把依赖列表(收集了很多依赖),循环通知一遍。

Observe.js

import Observer from './Observer'

// 入口文件 实现动态监听  
// 创建observe 函数
export default function observe (value) {
  //如果 value 不是对象, 直接返回(只能为对象服务)
  if (typeof value !== 'object') return;
  // 定义 ob
  var ob;
  // __ob__ 存储 Observer类 的实例
  if (typeof value.__ob__ !== 'undefined') {
    ob = value.__ob__;
  } else {
    ob = new Observer(value);
  }

  return ob;
}

Observer.js

import { def } from './util.js'
import defineReactive from './defineReactive.js'
import { arrayMethods } from './array.js'
import observe from './Observe.js'
import Dep from './Dep.js'


// 把对象 转换 成 每个层级的属性 都是响应式的对象(可以被侦测); 比如: obj:{a:{b:{c: 5}}}
export default class Observer {
  constructor(value) {
    // *new add* 每一个Observer的实例身上,都有一个Dep(也就是说__ob__上会有 dep). 目的是挂载上dep
    //  *new add* 每一个 Observer 实例上会有 dep 用来收集  数组 的依赖 
    //  *new add* 在对对象调用 defineReactive 实现依赖收集时,如果 对象上 某一项是 数组或对象,就会 收集依赖 到 这个 dep上
    //  以便于 没有触发 defineReactive 的 set 方法中 dep.notify(),我们可以 使用 Observer 实例上的 __ob__ 上的 另一个 dep 触发 notify
    //  Observer实例上的 dep 和 Observer实例上(对象)上 某一项 obj[key] 中 key 的 dep 收集的是同一个依赖。所有 修改 obj[key] 或者 调用 数组的push等等触发的 __ob__.dep.notify 都会 通知依赖更新
    /**
     比如: 
      obj1={list1: [1,2,3]}  => 

      Observe(obj1) ;=> 

      生成Observer实例  obj1_Observer = new Observer(obj1) => 

      实例 生成一个Dep实例 dep1  obj1_Observer__ob__dep1 

      然后 对 obj1.list1 调用 defineReactive(obj1, list1)

      会生成一个新的Dep 实例 dep2 (监听 obj1.list1的修改)
      
      同时  obj1.list1对应的 [1,2,3] 是一个数组或对象 
      又会调用 observe([1,2,3]) 
      返回一个新的 Observer实例 observer2 

      observer2 上又会产生一个 新的Dep实例 dep3  observer2__ob__dep3

      当在 在 get 访问中 访问 obj1.list1时, 会收集 dep2的依赖,并且收集dep3。(由于访问都是又Watcher触发的,所有收集的都是相同的依赖)

      当修改对象 的具体 数值的时候, 会触发 defineReactive中的set 方法, 然后 set 方法 可以访问 到 dep2, 所有调用dep2.notify触发依赖更新
      
      由于数组的更改比如push,shift,splice等无法触发 defineReactive中的set 方法 。我们  修改 了 Observer实例 observer2  中的 数组 方法。  
      当调用数组修改后 的方法时,我们 就调用  Observer实例 observer2 上的 observer2__ob__dep3   dep3.notify 触发依赖 更新


      总结来说。我们对 对象 某一项 转换为响应式的 时候,不仅仅 收集 可以通过defeineProperty修改某一项的 依赖; 还留一个口, 用来无法通过 defeineProperty监听的数据,我们通过其他方法 收集依赖
      比如上面我们通过 修改 obj1.list1 = 2 可以触发 dep2 更新依赖
      通过 obj1.list1.push(3) 可以触发 dep3 更新依赖
      而依赖都是同一个 Watcher 。所有都是处理 同一个事件,比如更新模板

     * **/ 
    this.dep = new Dep()

    // 给value(对象) 添加了 __ob__ 的属性,值是 这次new 的实例(构造函数中的this 表示的是实例),并且不可枚举
    def(value, '__ob__', this, false)

    // 检查value 是不是 数组
    if (Array.isArray(value)) {
      // 如果value是数组, 把value的原型,指向 arrayMethods(push等7个方法 被 改写过)
      Object.setPrototypeOf(value, arrayMethods) // 或者用 value.__proto__ = arrayMethods
      // 数组 实现响应式
      this.observeArray(value)

    } else {
      // 对象 直接 调用walk 方法  转换 为响应式
      this.walk(value)
    }
  }
  // 遍历 value对象上的每一个 属性,并且 调用 defineReactive方法 转换为响应式
  walk (value) {
    for (let k in value) {
      defineReactive(value, k)
    }
  }

  // 数组的特殊遍历 (数组的每一项都要调用 转换为响应式 ,可能数组里面也有数组或对象)
  observeArray (arr) {
    for (let i = 0, l = arr.length; i < l; i++) {
      // 逐项进行observe
      observe(arr[i])
    }
  }
}

util.js

// 定义对象  并且属性是否可以被枚举
export const def = function (obj, key, value, enumerable) {
  Object.defineProperty(obj, key, {
    value,
    enumerable, // 是否可枚举
    writable: true, //可写
    configurable: true, //可删除
  })
}

array.js


// 重写 数组的七个方法 以便实现响应式, 把重写后的 方法暴露出去
// 为什么要有这个方法 。改变对象的值,可以触发依赖更新。但是 数组的 push,pop等方法不会触发依赖更新。所有需要重写数组的七个方法 以实现 使用push,pop等方法时触发依赖更新

import { def } from './util.js'

// 得到 Array.prototype
const arrayPrototype = Array.prototype

// 需要被改写的 数组方法
const methodsNeedChange = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']

// 以 Array.prototype 为原型创建 arrayMethods 对象
export const arrayMethods = Object.create(arrayPrototype)

methodsNeedChange.forEach(methodName => {
  // 备份原来的方法, 方法原来的功能需要保留
  const original = arrayPrototype[methodName]

  // 定义新的方法(方法是对象,所有可以调用前面封装的def方法) 
  def(arrayMethods, methodName, function () {

    // 恢复原来的功能,执行数组方法
    const result = original.apply(this, arguments)

    // push, unshift, splice 会插入新值。 新值需要调用observe方法也 转换为 响应式
    let inserted = null

    // 获得 数组身上 的__ob__
    const ob = this.__ob__;

    // arguments 是类数组 ,没有slice方法 ,把arguments 转换为数组
    const ars = [...arguments]

    switch (methodName) {
      case 'push':
      case 'unshift':
        inserted = arguments;
        break;
      case 'splice':
        // splice (位置,删除几项, 插入的第1个值,插入的第2个值,插入的第3个值。。。)
        inserted = ars.slice(2)
        break;
    }

    // 判断有没有inserted, 有新值 也变为响应式
    if (inserted) {
      ob.observeArray(inserted)
    }
    console.log('触发array.js_啦啦啦啦啦啦');

    // *new add* 这里是 数组变化  触发依赖
    ob.dep.notify()

    // pop,shift 会有返回值
    return result

  }, false)
})

// *new add* dependArray() 收集儿子的依赖
export function dependArray(value) {
  for (let i = 0; i < value.length; i++) {
      let currentItem = value[i]
      currentItem.__ob__&& currentItem.__ob__.dep.depend()
      if(Array.isArray(currentItem)){
          dependArray(currentItem) // 递归收集依赖
      }
  }
}

defineReactive.js


import observe from './Observe.js'
import Dep from './Dep.js'
import { dependArray } from './array.js'


// 包裹 Object.defineProperty, 把 对象上的属性  转换为响应式
// (为什么要包裹? 因为:如果key为常量的时,get返回值永远是一个值。包裹之后才能 修改); 
export default function defineReactive (data, key, val) {
  // *new add* 获取Dep 的实例 便于收集和触发依赖 
  // *new add* 每一个 对象监听的 属性,都需要一个 dep 收集依赖
  // *new add* 数组的依赖 需要到 Observer上的一个dep上 ,下面的
  const dep = new Dep()


  // 如果值传入两个参数 ,那么 val 就是  对象对应的值
  if (arguments.length === 2) {
    val = data[key]
  }

  // *new add* 如果val 是对象,对象上的属性也要变化动态的。如果不是对象(常数、undefined等),observe函数里面不会处理
  // *new add* 子元素也要 observe; (observe,observer,defineReactive三个函数相互调用,形成了类似递归的效果)
  // *new add* observe 返回 Observer的实例, 实例上有 收集数组 的dep 
  let chilOb = observe(val)

  Object.defineProperty(data, key, {
    enumerable: true, //可以被枚举
    configurable: true, // 该属性的描述符能被改变,能被删除
    // 访问的时候触发 get函数
    get () {
      // *new add*  如果处于依赖收集阶段, 那么就收集依赖
      if (Dep.target) {
        dep.depend()
        // 如果子元素 存在的话, 也要收集依赖
        if (chilOb) {
          chilOb.dep.depend() // *new add* 数组收集当前渲染的watcher
          dependArray(val) // *new add* 收集儿子的依赖
        }
      }
      return val
    },
    // 修改的时候触发 set 函数
    set (newValue) {
      if (val === newValue) return;
      val = newValue

      // 当设置 新值的时候也要调用  observe 方法把 新值(如果是对象)转换为响应式
      chilOb = observe(newValue)

      // *new add*  这里是对象 改变 触发依赖 通知 dep
      // data.__ob__.notify()
      dep.notify()
    }
  })
}

Dep.js

var uid = 0;
// Dep 类收集 依赖 然后 触发依赖更新 (其中每一个依赖都是Watcher实例)
export default class Dep {
  constructor() {
    this.id = uid++;
    // 存储自己的订阅者(收集的watcher实例)。
    this.subs = []
  }

  // 添加订阅
  addSub (sub) {
    this.subs.push(sub)
  }

  // 添加依赖
  depend(){
    // Dep.target 自定义的一个全局唯一位置。也可以用window.__myOnly__随便啥都行;
    // Dep.target 上是正在读取数据的watcher, 之后会重置为null;
    console.log('depend', Dep.target);
    if(Dep.target){
      this.addSub(Dep.target)
    }
  }

  // 触发依赖 通知更新
  notify () {
    // 浅克隆
    const subs = this.subs.slice();
    // 遍历
    for (let i = 0, l = subs.length; i < l; i++) {
      // 每一个 watcher 调用 update更新方法
      subs[i].update()
    }
  }

}

Watcher.js

import Dep from './Dep'

// 监听对象 的表示式  触发回调
// vm.$watch('a.b.c.d', ()=>{})   a.b.c.d是下面的表达式
var uid = 0;
export default class Watcher {
  constructor(target, expressiong, callback) {
    this.id = uid++;
    // 需要监听的对象 
    this.target = target;
    // 获取 表达式 解析后的函数
    this.getter = parsePath(expressiong)
    // 回调
    this.callback = callback;
    // 获取 Watcher 实例的 值
    this.value = this.get()
  }
  // 更新
  update () {
    this.getAndInvoke(this.callback)
  }
  // 得到并唤起
  getAndInvoke (cb) {
    const value = this.get()

    // 如果更新后 到的值不是 当前的值 或者 是一个对象  。 那么更新value 并执行回调 
    if (value !== this.value || typeof value == 'object') {
      const oldValue = this.value;
      this.value = value
      cb.call(this, value, oldValue)
    }
  }
  // 获取
  get () {
    console.log('进入wathcer');
    // 进入依赖收集 阶段 (把watcher 设置全局唯一位置,以便Dep收集 当前的watcher实例)
    Dep.target = this;

    const obj = this.target

    // 获取 对象中表示式 对应的值
    let value;
    // 只要能找,就一直找
    try {
      // obj对象 在watcher 实例之前 就已经observe了。 (此时要获取值,就会触发 getter方法)
      // 所以这个时候 调用this.getter(obj) 会触发 defineReactive 的getter方法 。
      // getter方法中 判断 如果 Dep.target存在(此时存在了,就是这个Watcher实例),就会收集 watcher实例到 dep中
      value = this.getter(obj)
    } finally {
      // get结束后 ,说明读结束了。要把全局位置 让出来
      Dep.target = null;
    }
    console.log('进入wathcer-end', Dep.target);
    return value
  }
}

// 把a.b.c.d 处理 返回一个函数  调用返回的函数 可以获取 函数的值 
// 比如   var fn = parsePath('a.b.c.d')  ;  fn({a:{b:{c:{d:88}}}}) 获取 88
function parsePath (str) {
  var list = str.split('.')
  return (obj) => {
    if (!obj) return;
    for (let i = 0; i < list.length; i++) {
      obj = obj[list[i]]
    }
    return obj
  }
}

你可能感兴趣的:(vue2-数据响应式原理(一))