vue2.x源码----响应式原理学习笔记

深入响应式原理
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
在这里插入图片描述

在这里插入图片描述

1.Object.defineProperty(obj, prop, descriptor)方法

方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

//默认为 false。
let obj = {};
Object.defineProperty(obj,'a',{
	value:3,
	enumerable:true,//是否可枚举。默认false
	writable: false,//可读写 默认为 false
	configurable:true, //true该属性也能从对应的对象上被删除。默认false
})
Object.defineProperty(obj,'b',{
	//getter
	get(){
	console.log('试图访问obj的b属性')
	}
	//setter
	set(val){
	console.log('试图改变obj的b属性')
	}
})
console.log(obj)
console.log(obj.a,obj.b);

2.defineReactive函数

在这里插入图片描述
defineReactive.js

import Dep from './Dep'
import { observe } from './observe'

export default function defineReactive (data, key, val) {
  // console.log('我是defineReactive', key)
  const dep = new Dep()
  if (arguments.length == 2) {
    val = data[key]
  }
  //子元素要进行observe,至此形成了递归,这个递归不是函数自己调用自己,而是多个函数、类循环调用
  let childOb = observe(val)
  Object.defineProperty(data, key, {
    // writable: false,//是否可写
    enumerable: true, //是否可枚举
    configurable: true,//是否可配置 如delete
    //getter
    get () {
      console.log('你正在访问' + key + '属性')
      if (Dep.target) {
        dep.depend()
      }
      return val
    },
    //setter
    set (newValue) {
      console.log('你正在改变' + key + '属性', newValue)
      if (newValue == val) {
        return
      }
      val = newValue
      childOb = observe(newValue)
      dep.depend()
      //发布订阅模式,通知dep
      dep.notify()
    }
  })
}

3.递归侦测对象全部属性

在这里插入图片描述
utils.js

export const def = function (obj, key, value, enumerable) {
  // console.log('我是utils')
  Object.defineProperty(obj, key, {
    value,
    enumerable,
    writable: true,
    configurable: true
  })
}

observe.js

// 创建Observe 函数   注意没有r
import Observer from './Observer.js'
export default function observe (value) {
  // console.log('我是observe')
  // 如果value不是对象 什么都不做
  if (typeof value !== 'object') return
  // 定义ob
  let ob
  if (typeof value.__ob__ !== 'undefined') {
    // console.log('0')
    ob = value.__ob__
  } else {
    ob = new Observer(value)
  }
  return ob

}

新建 Observer.js(
将一个正常的Object转换为每个层级的属性都是响应式的object)

import { def } from './utils'
import defineReactive from './defineReactive.js'
import { arrayMethods } from './array'
import { observe } from './observe'
import Dep from './Dep'
// Observer 类 将一个正常的object转换为每个层级的属性都是响应式(可以被侦测的object)
export default class Observer {
  constructor(value) {
    this.dep = new Dep()
    //给实例(this,一定要注意,构造函数中的this不是表示类本身,而是表示实例)
    // value.__ob__ = this
    def(value, '__ob__', this, false)
    // console.log('我是Observer构造器', value) 
    //不要忘记初心,Observer类的目的是:将一个正常的Object转换为每个层级的属性都是响应式(可以被侦测的)的object
    //检查它是数组还是对象
    if (Array.isArray(value)) {
      //如果是数组,要非常强行的蛮干,将这个数组的原型,指向arrayMethods
      Object.setPrototypeOf(value, arrayMethods)
      this.observerArray(value)
    } else {
      this.walk(value)
    }
  }
  //对象遍历
  walk (value) {
    for (let k in value) {
      defineReactive(value, k)
    }
  }
  //数组遍历
  observerArray (arr) {
    for (let i = 0, l = arr.length; i < l; i++) {
      //逐项进行observe
      observe(arr[i])
    }
  }
}

4.数组的响应式处理(array.js)

在这里插入图片描述

import { def } from './utils'

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


//要被改写的7个数组方法
const methodsNeedChange = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]
methodsNeedChange.forEach(methodName => {
  //备份原来的方法 因为改写的7个函数的功能不能被剥夺
  const original = arrayPrototype[methodName]
  //定义新的方法
  def(arrayMethods, methodName, function () {
    //恢复原来的功能
    const result = original.apply(this, arguments)
    const args = [...arguments]
    //  把这个数组身上的__ob__属性取出来
    // __ob__已经被添加了? 因为数组肯定不是最高层,比如obj.g属性是数组,第一次遍历obj这个对象的第一层的时候
    // 已经给g属性  就是这个数组 添加了__ob__属性
    const ob = this.__ob__
    // 有三种方法push,unshift,splice能够插入新项,现在要把插入的新项也要observe的
    let inserted = []
    switch (methodName) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        // splice格式是(下标,数量,新项)
        inserted = args.slice(2)
        break
    }
    // 判断有没有要插入的新项,让新项也变为响应的
    if (inserted) {
      console.log('判断有没有要插入的新项')
      ob.observerArray(inserted)
    }
    console.log('啦啦啦')
    ob.dep.notify()
    return result
  }, false)
})

5.依赖收集(Dep.js)

1什么是依赖。
在这里插入图片描述
2 Dep类和Watcher类
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


var uid = 0
export default class Dep {
  constructor() {
    console.log('我是Dep类的构造函数')
    this.id = uid++
    // 用数组存储自己的订阅者,这个数组里放的是Watcher的实例
    this.subs = []
  }
  // 添加订阅
  addSub (sub) {
    this.subs.push(sub)
  }
  // 添加依赖
  depend () {
    // Dep.target 实际上就是我们自己指定的一个全局位置
    if (Dep.target) {
      this.addSub(Dep.target)
    }
  }
  // 通知更新
  notify () {
    console.log('我是notify')
    // 浅克隆一份
    const subs = this.subs.slice()
    // 遍历
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

Watcher.js

import Dep from "./Dep.js"

var uid = 0
export default class Watcher {
  constructor(target, expression, callback) {
    console.log('我是watcher构造函数')
    this.id = uid++
    this.target = target
    this.getter = parsePath(expression)
    this.callback = callback
    this.value = this.getter()
  }
  update () {
    this.run()
  }
  run () {
    this.getAndInvoke(this.callback)
  }
  getAndInvoke (cb) {
    const value = this.get()
    if (value !== this.value || typeof value == 'object') {
      const oldValue = this.value
      this.value = value
      cb.call(this.target, value, oldValue)
    }
  }
  get () {
    // 进入依赖收集阶段
    // 让全局的Dep.target设置为Watcher本身,那么就是进入依赖收集阶段
    Dep.target = this
    const obj = this.target
    var value
    try {
      value = this.getter(obj)
    } finally {
      Dep.target = null
    }
    return value
  }

}
function parsePath (str) {
  let segments = str.split('.')
  console.log(segments)
  return (obj) => {
    for (let i = 0; i < segments.length; i++) {
      obj = obj[segments[i]]
    }
    return obj
  }
}

github 仓库地址 webpack5环境

https://github.com/pitersu/vue2.x-

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值