前言
1. MVVM 模式
模板
<p>我今年
{{age}}
岁了
数据
this.age ++
model <=view-model=>
view
2. 侵入式和非侵入式
侵入式
(React-数据变化)
this.setState({ n: this.n + 1 })
(小程序-数据变化)
this.setData({ m: this.m + 1 })
非侵入式
(Vue-数据变化)
this.num ++
非侵入式实现原理
核心是
object.definefroperty()
进行数据劫持/数据代理利用javascript引擎方法,检测对象属性变化
3. 什么是响应式系统?
数据改变时,视图对应的更新,就是响应式系统。
实现响应式系统的核心内容
1. 数据劫持
2. 依赖收集
3. 派发更新
一、数据劫持
1. Object.defineProperty
用Object.defineProperty
进行数据劫持
const data = {}
let value = 100
Object.defineProperty(data, key, {
get() {
console.log(`访问key属性`)
return value
},
set(newValue) {
if (value === newValue) return
console.log(`改变key属性`, newValue)
value = newValue
}
})
如上所示
get()
时能监听到数据的访问(取值),set()
时能监听到数据的改变(赋值)。
我们可以在get()
和set()
时对数据做一些处理,重写原有的行为,这就是数据劫持
的意义。
2. defineReactive
为方便使用,对Object.defineProperty
做封装处理:defineReactive()
function defineReactive (data, key, value) {
if (arguments.length === 2) {
value = data[key]
}
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get () {
// console.log(`访问${key}属性`)
return value
},
set (newValue) {
// console.log(`改变${key}属性`, newValue)
if (value === newValue) return
value = newValue
}
})
}
使用 defineReactive(data, key, value)
const obj = {
a: 100,
b: 200
};
// 添加属性
defineReactive(obj, c, 300)
console.log(obj) // {a: 100, b: 200, c: 300}
// 修改属性
defineReactive(obj, a, 1000)
console.log(obj) // {a: 1000, b: 200, c: 300}
由上可知,我们在访问、添加、修改时能检测到数据。
3. Observer 类
问题1:如果obj
有多个属性时,我们需要怎么监测???
我们需要新建一个类Observer 类
来遍历该对象的所有属性和属性下的每个层级的属性。
Observer
就是一个观察者,用来做数据侦测。就是将一个正常的object转换为每个层级的属性都是响应式的(可以被侦测的)object
class Observer {
constructor(value) {
// 给value添加__ob__属性,值为new的实例ob,并设置不可枚举。
// 【ob = Observe,即 ob = value.__ob__ 或者 new OBserver(value)】
def(value, '__ob__', this, false) // 给对象每层添加__ob__属性
this.walk(value)
}
walk (value) {
// 对象遍历,对 value 所有子元素进行defineReactive绑定
for (const key in value) {
defineReactive(value, key)
}
}
}
创建Observer
类时,我们用到了def
函数,def
函数是用来设置对象操作设置的,这里是用来设置属性值
和枚举权限
的。还有是否可删除、修改等设置...
const def = (data, key, value, enumerable) => {
Object.defineProperty(data, key, {
value,
enumerable, // 是否可枚举
writable: true, // 是否可修改
configurable: true // 是否可删除
})
}
测试一下 Observer
,遍历所有属性
const obj = { a: 1, b: 2 }
new Observer(obj)
console.log(obj.a); // 访问a属性,1
console.log(obj.b); // 访问a属性,2
obj.a = 11 // 改变a属性,11
obj.b = 22 // 改变b属性,22
以下为测试结果:
如图所示,所有属性都被检测到,切给对象添加了__ob__属性
4. Observe 函数
问题2:如果obj
内有嵌套的属性时,我们需要怎么监测???
我们需要Observ
函数,来帮助我们完成对子属性的监测绑定
Observe 函数,是用来在绑定对象监测的,是Observer的扩展工具函数,用于设置Observer观察者。(给对象添加
__ob__
属性)
function Observe (value) {
// 不是对象不处理
if (typeof value != 'object') return
// 定义ob
let ob
if (typeof value.__ob__ != 'undefined') {
// 判断当前对象上是否有__ob__属性,有则直接使用
ob = value.__ob__
} else {
// 若对象本层没有__ob__就 new Oberser() 添加
ob = new Observer(value)
}
return ob
}
修改 defineReactive
函数,进入defineReactive
函数就立即进行observe(),在发生改变set()
时,对新值也要进行observe()
function defineReactive (data, key, value) {
if (arguments.length === 2) {
value = data[key]
}
`对当前对象和对象子元素进行Oberse,形成循环递归`
`let childOb = Observe(value)`
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get () {
// console.log(`访问${key}属性,${value}`)
return value
},
set (newValue) {
// console.log(`改变${key}属性,${newValue}`)
if (value === newValue) return
value = newValue
`假如有新值,那么Oberse绑定新值`
`childOb = Observe(newValue)`
}
})
}
测试Observe
函数
import Observe from './Observe'
let obj = {
a: {
a1: {
a11: 11
},
a2: 12
},
b: {
b1: 22
}
}
Observe(obj) // 监测obj
console.log(obj);
以下为测试结果:
如图所示,我们给对象的每一层未对象的属性添加了__ob__,实现了对对象的每一层的数据劫持
。
5. 总结数据劫持的执行过程
执行observe(obj)
├── new Observer(obj),并执行this.walk()遍历obj的属性,执行defineReactive()
`├── defineReactive(obj, a)`
├── 执行observe(obj.a) 发现obj.a是对象
├── 执行 new Observer(obj.a),遍历obj.a的属性,执行defineReactive()
`├── defineReactive(obj.a, a1)`
├── 执行observe(obj.a.a1) 发现obj.a是对象
├── 执行 new Observer(obj.a.a1) 遍历obj.a.a1的属性
├── defineReactive(obj.a.a1, a11)
├── 执行observe(obj.a.a1.a11) obj.a.a1.a11不是对象,直接返回
├── 执行defineReactive(obj.a.a1, a11)的剩余代码
`├── defineReactive(obj.a, a2)`
├── 执行observe(obj.a.a2) 发现obj.a不是对象
├── 执行defineReactive(obj.a, a2)的剩余代码
`├── defineReactive(obj, b)`
├── 执行observe(obj.b) 发现obj.b是对象
├── 执行 new Observer(obj.b),遍历obj.b的属性,执行defineReactive()
├── 执行defineReactive(obj.b, b1)
├── 执行observe(obj.b.b1) 发现obj.b.b1不是对象,直接返回
├── 执行defineReactive(obj.b, b1)的剩余代码
├── 执行defineReactive(obj, b)的剩余代码
代码执行结束
二、依赖收集
1. 发布和订阅模式
什么是 发布和订阅模式
?
举个例子,微博关注明星。如花
是国内体育明星,粉丝千万,小a 是其粉丝之一,小a 想了解 如花
的动态,于是在微博关注 如花
,当 如花
更新微博时,微博就会推送 如花
动态给 小a
通过以上 微博关注明星 的例子,我们可以把其逻辑抽象理解为:发布和订阅模式
。
粉丝关注 如花
= 在微博登记自己的信息对 如花
进行订阅,微博会把订阅者信息存到一个数组中, 如花
在更新状态时,微博会遍历数组,逐个通知订阅者
2. Watcher 类
在响应式系统中,也有储存粉丝信息的数组,就是Watcher
通过例子我们知道,Watcher有三个核心功能:
- 有一个数组来存储
Watcher
Watcher
实例需要订阅(依赖)数据,也就是获取依赖或者收集依赖Watcher
的依赖发生变化时触发Watcher
的回调函数,也就是派发更新。
实现 Watcher
类
class Watcher {
constructor(data, expression, callback) {
this.data = data // 要观察的对象
this.expression = expression // 要观察的对象具体哪个属性
this.callback = callback // 发布动作,订阅的属性有变化时触发回调函数
this.value = this.get() // 默认在实例化时就获取到,要观察对象的具体属性的值
}
get () {
// 进行依赖收集,让全局的Dep.target设置为Watcher本身,那么就是进入了依赖收集阶段
window.target = this // 存放 Watcher 数组位置的地方
let value
try {
value = parsePath(this.expression)(this.data)
} finally {
window.target = null
}
return value
}
// 更新
update () {
const value = this.get()
// 如果新值不等于旧值,或者新值为对象
if (value !== this.value || typeof value == 'object') {
// 存旧值为 oldValue
const oldValue = this.value
// 让this.value=新值
this.value = value
this.callback.call(this.data, value, oldValue)
}
}
}
// 工具函数 parsePath
const parsePath = (str) => {
let segments = str.split('.')
return (obj) => {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
测试一下 Watcher
类
let obj = {
a: {
b: 100,
c: 200
}
}
const str_b = new Watcher(obj, 'a.b') // 订阅obj对象内的a.b
const str_c = new Watcher(obj, 'a.c') // 订阅obj对象内的a.c
console.log('str_b', str_b.value) // str_b 100
console.log('str_c', str_c.value) // str_c 200
上述测试,我们可以通过对象订阅,获取到对象对应的值。但是在数据变化时我们不知道,所以还需要把数据劫持和依赖收集结合一下
实现 Dep 类
结合defineReactive
函数的set()
方法,在数据更新时,我们能知道数据发生变化了,能拿到新数据,并通知发布。每个数据都应该维护一个属于自己的数组,该数组来存放依赖自己的watcher
,我们可以在defineReactive
中定义一个数组dep
,这样通过闭包,每个属性就能拥有一个属于自己的dep
修改 defineReactive
函数
function defineReactive (data, key, value) {
`const dep = new Dep()`
if (arguments.length === 2) {
value = data[key]
}
// 子元素进行Oberse,形成循环递归
let childOb = Observe(value)
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get () {
// 如果现在处于依赖收集阶段
`订阅收集,如果有子属性,对子属性也进行收集`
dep.depend()
if (childOb) {
childOb.dep.depend()
}
return value
},
set (newValue) {
if (value === newValue) return
value = newValue
// 假如有新值,那么Oberse绑定新值
childOb = Observe(newValue)
}
})
}
实现 Dep 类
class Dep {
constructor() {
this.id = uid++
// subscribes简写,用来储存订阅者,这个数组内放的是watcher的实例
this.subs = []
}
// 通知发布更新
notfiy () {
// 通知所有订阅者更新
[...this.subs].forEach(sub => sub.update())
}
// 添加依赖
depend () {
if (Dep.target) {
this.addSub(Dep.target)
}
// console.log('depend')
}
// 添加订阅
addSub (sub) {
this.subs.push(sub)
}
}
三、派发更新
实现依赖收集后,我们最后要实现的功能是派发更新,也就是依赖变化时触发watcher
的回调。从依赖收集部分我们知道,获取哪个数据,也就是说触发哪个数据的get
,就说明watcher
依赖哪个数据,那数据变化的时候通知watcher
:在set
中触发派发更新。
set (newValue) {
if (value === newValue) return
value = newValue
// 假如有新值,那么Oberse绑定新值
childOb = Observe(newValue)
`发布订阅,通过dep.notfiy去通知dep`
dep.notfiy()
}
四、补充,对数组特殊处理
我们访问和获取arr
的值,get
和set
会被触发,但是如果arr.push()
呢?数组的每个元素要依次向后移动一位,这就会触发get
和set
,导致依赖发生变化。由于数组是顺序结构,所以索引(key)和值不是绑定的,因此这种护理方法是有问题的。
vue
对js
中7种会改变数组的方法进行了重写:push, pop, unshift, shift, splice, reverse, sort
。
改写这7个方法的重要环节就是:代理原型
// 获取数组的原型
const ArrayPrototype = Array.prototype
// 设置array的代理原型
// Object.create() 方法创建一个新对象 ArrayMethods,新对象的__proto__ 为 ArrayPrototype
export const proxyArrayPrototype = Object.create(ArrayPrototype)
// 需要改写的数组方法
const rewriteArrayMothods = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse',
]
// 进行改写 proxyArrayPrototype
rewriteArrayMothods.map(methodName => {
// 获取原型上的方法
const original = ArrayPrototype[methodName]
def(proxyArrayPrototype, methodName, function () {
// 把original的this指向到 proxyArrayPrototype
const res = original.apply(this, arguments)
// 因为最外层肯定是对象,所有__ob__一定存在
const ob = this.__ob__
// push,shift,splice会插入数据,arguments拿到新插入的数据
let insert = []
switch (methodName) {
case 'push':
case 'shift':
insert = [...arguments]
break;
case 'splice':
// splice(index,howmany,'新增的内容') 第三个参数为新增的数组元素,索引为2
insert = [...arguments].slice(2)
break;
}
// 如果有新插入的数据,那么对新插入的数据进行observe绑定(监听)
if (insert.length > 0) ob.observeArray(insert)
// 数组发生变化,也要通知dep
ob.dep.notfiy()
// console.log('操作数组~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
return res
}, false)
})
由上代码,我们看出,在 proxyArrayPrototype
代理原型上,我们改写了7个方法
主要意义是在触发这个7个方法时,做两件事:
- 进行
dep.notfiy()
发布更新 - 当数组插入新数据时,进行添加依赖(添加订阅)
ob.observeArray(insert)
对Array的代理原型上7个方法改造完之后,我们需要修改 Observer 类
当Observer
观察的数据类型为数组
时:
- 我们要把
数组的原型
指向代理原型:
Object.setPrototypeOf(value, proxyArrayPrototype)
。 - 需要把数组的每一项进行
Oberse
监听
class Observer {
constructor(value) {
this.dep = new Dep()
// this === 实例本身,而不是类的本身
// 给value添加__ob__属性,值为new的实例ob,并设置不可枚举。
//【ob = Observe,即 ob = value.__ob__ 或者 new OBserver(value)】
def(value, '__ob__', this, false)
if (Array.isArray(value)) {
// value === 数组,将value数组的原型,指向代理原型 proxyArrayPrototype
Object.setPrototypeOf(value, proxyArrayPrototype)
// value === 数组,需要把数组的每一项进行Oberse监听
this.observeArray(value)
} else {
// value === 对象或者字符串,对 value 所有子元素进行defineReactive绑定
this.walk(value)
}
}
walk (value) {
// 对象遍历,对 value 所有子元素进行defineReactive绑定
for (const key in value) {
defineReactive(value, key)
}
}
observeArray (value) {
for (let i = 0, j = value.length; i < j; i++) {
Observe(value[i])
}
}
}
五、代码整理
defineReactive.js
import Observe from './Observe'
import Dep from "./Dep"
export default function defineReactive (data, key, value) {
const dep = new Dep()
if (arguments.length === 2) {
value = data[key]
}
// 子元素进行Oberse,形成循环递归
let childOb = Observe(value)
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get () {
// console.log(`访问${key}属性,${value}`)
// 如果现在处于依赖收集阶段
dep.depend()
if (childOb) {
childOb.dep.depend()
}
return value
},
set (newValue) {
// console.log(`改变${key}属性,${newValue}`)
if (value === newValue) return
value = newValue
// 假如有新值,那么Oberse绑定新值
childOb = Observe(newValue)
// 发布订阅模式,通过dep.notfiy去通知dep
dep.notfiy()
}
})
}
Observe.js
import Observer from "./Observer"
export default function Observe (value) {
// 不是对象不处理
if (typeof value != 'object') return
// 定义ob
let ob
if (typeof value.__ob__ != 'undefined') {
// 判断当前对象上是否有__ob__,有则直接使用
ob = value.__ob__
} else {
// 若对象本层没有__ob__就 new Oberser() 添加观察者
ob = new Observer(value)
}
return ob
}
Observer.js
import { def } from './utils'
import defineReactive from './defineReactive'
import Observe from './Observe'
import { proxyArrayPrototype } from "./array"
import Dep from "./Dep"
// ? Observer : 观察者,数据侦测。 将一个正常的object转换为每个层级的属性都是响应式的(可以被侦测的)object
export default class Observer {
constructor(value) {
this.dep = new Dep()
// this === 实例本身,而不是类的本身
// 给value添加__ob__属性,值为new的实例ob,并设置不可枚举。 【ob = Observe,即 ob = value.__ob__ 或者 new OBserver(value)】
def(value, '__ob__', this, false)
// console.log(`Observer构造器`, value);
if (Array.isArray(value)) {
// value === 数组,将value数组的原型,指向代理原型 proxyArrayPrototype
Object.setPrototypeOf(value, proxyArrayPrototype)
// value === 数组,需要把数组的每一项进行Oberse监听
this.observeArray(value)
} else {
// value === 对象或者字符串,对 value 所有子元素进行defineReactive绑定
this.walk(value)
}
}
walk (value) {
// 对象遍历,对 value 所有子元素进行defineReactive绑定
for (const key in value) {
defineReactive(value, key)
}
}
observeArray (value) {
for (let i = 0, j = value.length; i < j; i++) {
Observe(value[i])
}
}
}
Dep.js
let uid = 0
export default class Dep {
constructor() {
this.id = uid++
// subscribes简写,用来储存订阅者,这个数组内放的是watcher的实例
this.subs = []
// console.log('dep构造器')
}
// 通知更新
notfiy () {
// console.log('notify')
// 通知所有订阅者更新
[...this.subs].forEach(sub => sub.update())
}
// 添加依赖
depend () {
if (Dep.target) {
this.addSub(Dep.target)
}
// console.log('depend')
}
// 添加订阅
addSub (sub) {
this.subs.push(sub)
}
}
Watcher.js
import { parsePath } from "./utils";
import Dep from './Dep'
let uid = 0
Dep.target = null // 一个全局变量,用来储存订阅数据,可以是任何变量,如:window.target
const TargetStack = []
const pushTarget = (_target) => {
TargetStack.push(Dep.target)
Dep.target = _target
}
const popTarget = (_target) => {
Dep.target = TargetStack.pop()
}
export default class Watcher {
constructor(data, expression, callback) {
// console.log('Watcher')
this.id = uid++
this.data = data // 要观察的对象
this.expression = expression // 要观察的对象具体哪个属性
this.callback = callback // 发布动作,订阅的属性有变化时触发回调函数
this.value = this.get() // 默认在实例化时就获取到,要观察对象的具体属性的值
}
get () {
// 进行依赖收集,让全局的Dep.target设置为Watcher本身,那么就是进入了依赖收集阶段
pushTarget(this)
let value
try {
value = parsePath(this.expression)(this.data)
} finally {
popTarget(this)
}
return value
}
// 更新
update () {
const value = this.get()
// 如果新值不等于旧值,或者新值为对象
if (value !== this.value || typeof value == 'object') {
// 存旧值为 oldValue
const oldValue = JSON.parse(JSON.stringify(this.value))
// 让this.value=新值
this.value = value
this.callback.call(this.data, value, oldValue)
}
}
}
array.js
import { def } from "./utils";
// 获取数组的原型
const ArrayPrototype = Array.prototype
// 设置array的代理原型
// Object.create() 方法创建一个新对象 ArrayMethods,新对象的__proto__ 为 ArrayPrototype
export const proxyArrayPrototype = Object.create(ArrayPrototype)
// 需要改写的数组方法
const rewriteArrayMothods = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse',
]
rewriteArrayMothods.map(methodName => {
// 获取原型上的方法
const original = ArrayPrototype[methodName]
def(proxyArrayPrototype, methodName, function () {
// 把original的this指向到 proxyArrayPrototype
const res = original.apply(this, arguments)
// 因为最外层肯定是对象,所有__ob__一定存在
const ob = this.__ob__
// push,shift,splice会插入数据,arguments拿到新插入的数据
let insert = []
switch (methodName) {
case 'push':
case 'shift':
insert = arguments
break;
case 'splice':
insert = arguments.slice(2) // splice(index,howmany,'新增的内容') 第三个参数为新增的数组元素,索引为2
break;
}
// 如果有新插入的数据,那么对新插入的数据进行observe绑定(监听)
if (insert.length > 0) ob.observeArray(insert)
// 数组发生变化,也要通知dep
ob.dep.notfiy()
return res
}, false)
})
utils.js
export const def = (data, key, value, enumerable) => {
Object.defineProperty(data, key, {
value,
enumerable,
writable: true,
configurable: true
})
}
export const parsePath = (str) => {
let segments = str.split('.')
return (obj) => {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
index.js 入口文件
import Observe from './Observe'
import Watcher from './Watcher'
let obj = {
a: {
a1: {
a11: 11
},
a2: 12
},
b: {
b1: 22
},
arr: [{ k: 111 }, 222, 333, 444]
}
Observe(obj)
// obj.a.ab1.ab1c1.ab1c1d1 = 1
// console.log('obj', obj);
// new Watcher(obj, 'a.a1.a11', (n, o) => {
// console.log('新值:', n, '旧值:', o,);
// })
// obj.a.a1.a11 = 1
// new Watcher(obj, 'a.a2', (n, o) => {
// console.log('新值:', n, '旧值:', o);
// })
// obj.a.a2 = 2
new Watcher(obj, 'arr', (n, o) => {
console.log(`新值:`, n, `旧值:`, o);
})
obj.arr.push(1000)