Vue2.x-数据响应式原理(笔记)
Vue2.x-数据响应式原理(笔记)
- 源码链接
- Vue2.x-数据响应式原理
- `Object.defineProperty()` 方法
- `defineReactive` 函数
- 递归侦测对象全部属性
- 数组的响应式处理
- 依赖收集
- 什么是依赖?
- `Dep` 类 和 `Watcher` 类
- 代码
源码链接
- https://github.com/wanghuan19961020/Vue2.x-Study-Notes
Vue2.x-数据响应式原理
Object.defineProperty() 方法
Object.defineProperty()方法会直接在一个对象定义一个属性,或者修改一个对象的现有属性,并返回此对象。
var obj = {}Object.defineProperty(obj, 'a', { value: 3 })
Object.defineProperty(obj, 'b', { value: 5 })console.log(obj)
console.log(obj.a, obj.b)
var obj = {}Object.defineProperty(obj, 'a', {// getterget() {console.log('访问a')},// setterset() {console.log('改变a')}
})Object.defineProperty(obj, 'b', { value: 5, enumerable: true })console.log(obj)
console.log(obj.a++, obj.b)
defineReactive 函数
getter/setter需要变量周转才能工作
var obj = {}
var temp
Object.defineProperty(obj, 'a', {// getterget() {console.log('访问a')return temp},// setterset(newValue) {console.log('改变a')temp = newValue}
})
defineReactive函数
function defineReactive(data, key, val) {// 闭包环境Object.defineProperty(data, key, {// 可枚举enumerable: true,// 可以被配置configurable: true,// getterget() {console.log('访问' + key)return val},// setterset(newValue) {console.log('改变' + key, newValue)if (val === newValue) returnval = newValue}})
}var obj = {}defineReactive(obj, 'a', 10)
console.log(obj.a)
obj.a = 60
obj.a += 10
console.log(obj.a)
递归侦测对象全部属性
Observer:讲一个正常的 object 转换为每个层级的属性都是响应式(可以被侦测的)的 objectindex.js
import observe from './observe'
var obj = {a: {m: {n: 10}},b: 20
}observe(obj)
obj.b++
console.log(obj.a.m.n)
observe.js
import Observer from './Observer'
// 创建 Observer 函数
export default function (value) {// 如果 value 不是对象,什么都不做if (typeof value !== 'object') return// 定义 obvar obif (typeof value.__ob__ !== 'undefined') {ob = value.__ob__} else {ob = new Observer(value)}return ob
}
Observer.js
import { def } from './utils'
import defineReactive from './defineReactive'
export default class Observer {constructor(value) {// 给实例(this 一定要注意,构造函数中的 this 不是表示类本身,而是表示实例)// 添加了 __ob__ 属性,值是这次 new 的实例def(value, '__ob__', this, false)console.log('Observer构造器', value)// Observer类的目的是:将一个正常的 object 转换为每个层级的属性都是响应式(可以被侦测的)的 objectthis.walk(value)}// 遍历walk(value) {for (const key in value) {defineReactive(value, key)}}
}
defineReactive.js
import observe from './observe'
export default function (data, key, val) {// 闭包环境if (arguments.length === 2) {val = data[key]}// 子元素要进行 observe,至此形成了递归,这个地柜不是函数自己调用自己,而是多个函数、类循环调用let childOb = observe(val)Object.defineProperty(data, key, {// 可枚举enumerable: true,// 可以被配置configurable: true,// getterget() {console.log('访问' + key)return val},// setterset(newValue) {console.log('改变' + key, newValue)if (val === newValue) returnval = newValue// 当设置了新值,这个新值也要被 observechildOb = observe(newValue)}})
}
utils.js
export const def = function (obj, key, value, enumerable) {Object.defineProperty(obj, key, {value,enumerable,writable: true,configurable: true})
}
数组的响应式处理
Vue底层改写了Array原生的 7 个方法:['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']- 这七个方法在
Array.prototype上 index.js
import observe from './observe'
var obj = {a: {m: {n: 10}},b: 20,g: [22, 33, 55]
}observe(obj)
obj.g.splice(2, 1, [666, 888])
obj.g.push(66)
console.log(obj)
// obj.b++
// console.log(obj.a.m.n)
// defineReactive(obj, 'a')
// console.log(obj.a.m.n)
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]// 把这个数组身上的 __ob__ 取出来,__ob__ 已经被添加了,为什么已经被添加了?因为数组肯定不是最高层// 比如 obj.g 属性是数组,obj 不能是数组,第一次遍历 obj 这个对象的第一层的时候,已经给 g 属性// (就是这个数组)添加了 __ob__ 属性// 定义新的方法def(arrayMethods,methodName,function () {// 恢复原来的功能const result = original.apply(this, arguments)const ob = this.__ob__// 将 arguments 变为数组const args = [...arguments]// 有三种方法 push/unshift/splice 能够插入新项,现在要把插入的新项也要变为 observe 的let insertedswitch (methodName) {case 'push':case 'unshift':inserted = argsbreakcase 'splice':// splice 格式是 splice(下标, 数量, 插入的新项)inserted = args.slice(2)break}// 判断有没有要插入的新项,让新项也变为响应的if (inserted) {ob.observeArray(inserted)}console.log('lalalala')return result},false)
})
Observer.js
import { def } from './utils'
import defineReactive from './defineReactive'
import { arrayMethods } from './array'
import observe from './observe'
export default class Observer {constructor(value) {// 给实例(this 一定要注意,构造函数中的 this 不是表示类本身,而是表示实例)// 添加了 __ob__ 属性,值是这次 new 的实例def(value, '__ob__', this, false)console.log('Observer构造器', value)// Observer类的目的是:将一个正常的 object 转换为每个层级的属性都是响应式(可以被侦测的)的 object// 检查它是数组还是对象if (Array.isArray(value)) {// 如果是数组,将这个数组的原型,指向 arrayMethodsObject.setPrototypeOf(value, arrayMethods)// 让这个数组变得 observethis.observeArray(value)} else {this.walk(value)}}// 遍历walk(value) {for (const key in value) {defineReactive(value, key)}}// 数组的特殊遍历observeArray(arr) {for (let i = 0, l = arr.length; i < l; i++) {// 逐项 observeobserve(arr[i])}}
}
依赖收集
什么是依赖?
- 需要用到数据的地方,称为依赖
Vue1.x,细粒度依赖,用到数据的DOM都是依赖Vue2.x,中等粒度依赖,用到数据的组件都是依赖- 在
getter中收集依赖,在setter中触发依赖
Dep 类 和 Watcher 类
-
把依赖收集的代码封装成一个
Dep类,它专门用来管理依赖,每个Observer的实例,成员中都有一个Dep的实例 -
Wacther是一个中介,数据发生变化时通过Wacther中转,通知组件


-
依赖就是
Watcher。只有Watcher触发的getter才会收集依赖,哪个Wacther触发了getter,就把哪个Watcher收集到Dep中。 -
Dep使用发布-订阅模式,当数据发生变化时,会循环依赖列表,把所有的Watcher都通知一遍。 -
代码实现的巧妙之处:
Watcher把自己设置到全局的一个指定位置,然后读取数据,因为读取了数据,所以会触发这个数据的getter。在getter中就能得到当前正在读取数据的Watcher,并把这个Watcher收集到Dep中。
代码
index.js
import observe from './observe'
import Watcher from './Watcher'
var obj = {a: {m: {n: 10}},b: 20,g: [22, 33, 55]
}observe(obj)
obj.a.m.n = 8
obj.g.splice(2, 1, [666, 888])
obj.g.push(66)
console.log(obj)
new Watcher(obj, 'a.m.n', (val) => {console.log('*我是Watcher,我在监控a.m.n', val)
})
obj.a.m.n = 8888
Observer.js
import { def } from './utils'
import defineReactive from './defineReactive'
import { arrayMethods } from './array'
import observe from './observe'
import Dep from './Dep'
export default class Observer {constructor(value) {// 在每个 Observer 的实例身上,都有一个 depthis.dep = new Dep()// 给实例(this 一定要注意,构造函数中的 this 不是表示类本身,而是表示实例)// 添加了 __ob__ 属性,值是这次 new 的实例def(value, '__ob__', this, false)console.log('Observer构造器', value)// Observer类的目的是:将一个正常的 object 转换为每个层级的属性都是响应式(可以被侦测的)的 object// 检查它是数组还是对象if (Array.isArray(value)) {// 如果是数组,将这个数组的原型,指向 arrayMethodsObject.setPrototypeOf(value, arrayMethods)// 让这个数组变得 observethis.observeArray(value)} else {this.walk(value)}}// 遍历walk(value) {for (const key in value) {defineReactive(value, key)}}// 数组的特殊遍历observeArray(arr) {for (let i = 0, l = arr.length; i < l; i++) {// 逐项 observeobserve(arr[i])}}
}
defineReactive.js
import observe from './observe'
import Dep from './Dep'
export default function (data, key, val) {const dep = new Dep()// 闭包环境if (arguments.length === 2) {val = data[key]}// 子元素要进行 observe,至此形成了递归,这个地柜不是函数自己调用自己,而是多个函数、类循环调用let childOb = observe(val)Object.defineProperty(data, key, {// 可枚举enumerable: true,// 可以被配置configurable: true,// getterget() {console.log('访问' + key)// 如果处于依赖收集阶段if (Dep.target) {dep.depend()if (childOb) {childOb.dep.depend()}}return val},// setterset(newValue) {console.log('改变' + key, newValue)if (val === newValue) returnval = newValue// 当设置了新值,这个新值也要被 observechildOb = observe(newValue)// 发布-订阅模式,通知 depdep.notify()}})
}
Dep.js
var uid = 0export default class Dep {constructor() {console.log('Dep构造器')this.id = uid++// 用数组存储自己的订阅者。subs是英语subscribes订阅者的意思// 这个数组里面放的是 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'var uid = 0function parsePath(str) {var segments = str.split('.')return (obj) => {for (let i = 0; i < segments.length; i++) {if (!obj) returnobj = obj[segments[i]]}return obj}
}export default class Watcher {constructor(target, expression, callback) {console.log('Watcher构造器')this.id = uid++this.target = targetthis.getter = parsePath(expression)this.callback = callbackthis.value = this.get()}update() {this.run()}run() {this.getAndInvoke(this.callback)}getAndInvoke(cb) {const value = this.get()if (value !== this.value || typeof value === 'object') {const oldValue = this.valuethis.value = valuecb.call(this.target, value, oldValue)}}get() {// 进入依赖收集阶段,让全局的 Dep.target 设置为 Watcher 本身,那么就是进入了依赖收集阶段Dep.target = thisconst obj = this.targetvar value// 只要能找,就一直找try {value = this.getter(obj)} catch (error) {console.log(error)} finally {Dep.target = null}return value}
}
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
