Vue2.x 数据响应式原理(笔记)
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', {
// getter
get() {
console.log('访问a')
},
// setter
set() {
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', {
// getter
get() {
console.log('访问a')
return temp
},
// setter
set(newValue) {
console.log('改变a')
temp = newValue
}
})
-
defineReactive
函数
function defineReactive(data, key, val) {
// 闭包环境
Object.defineProperty(data, key, {
// 可枚举
enumerable: true,
// 可以被配置
configurable: true,
// getter
get() {
console.log('访问' + key)
return val
},
// setter
set(newValue) {
console.log('改变' + key, newValue)
if (val === newValue) return
val = newValue
}
})
}
var obj = {}
defineReactive(obj, 'a', 10)
console.log(obj.a)
obj.a = 60
obj.a += 10
console.log(obj.a)
递归侦测对象全部属性
-
Observer
:讲一个正常的 object 转换为每个层级的属性都是响应式(可以被侦测的)的 object index.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
// 定义 ob
var ob
if (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 转换为每个层级的属性都是响应式(可以被侦测的)的 object
this.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,
// getter
get() {
console.log('访问' + key)
return val
},
// setter
set(newValue) {
console.log('改变' + key, newValue)
if (val === newValue) return
val = newValue
// 当设置了新值,这个新值也要被 observe
childOb = 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 inserted
switch (methodName) {
case 'push':
case 'unshift':
inserted = args
break
case '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)) {
// 如果是数组,将这个数组的原型,指向 arrayMethods
Object.setPrototypeOf(value, arrayMethods)
// 让这个数组变得 observe
this.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++) {
// 逐项 observe
observe(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 的实例身上,都有一个 dep
this.dep = new Dep()
// 给实例(this 一定要注意,构造函数中的 this 不是表示类本身,而是表示实例)
// 添加了 __ob__ 属性,值是这次 new 的实例
def(value, '__ob__', this, false)
console.log('Observer构造器', value)
// Observer类的目的是:将一个正常的 object 转换为每个层级的属性都是响应式(可以被侦测的)的 object
// 检查它是数组还是对象
if (Array.isArray(value)) {
// 如果是数组,将这个数组的原型,指向 arrayMethods
Object.setPrototypeOf(value, arrayMethods)
// 让这个数组变得 observe
this.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++) {
// 逐项 observe
observe(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,
// getter
get() {
console.log('访问' + key)
// 如果处于依赖收集阶段
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
}
}
return val
},
// setter
set(newValue) {
console.log('改变' + key, newValue)
if (val === newValue) return
val = newValue
// 当设置了新值,这个新值也要被 observe
childOb = observe(newValue)
// 发布-订阅模式,通知 dep
dep.notify()
}
})
}
Dep.js
var uid = 0
export 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 = 0
function parsePath(str) {
var segments = str.split('.')
return (obj) => {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
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.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.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)
} catch (error) {
console.log(error)
} finally {
Dep.target = null
}
return value
}
}