- 目的:当数据改变时,要能监听到数组或对象上值的改变,并且进行自己的操作。
- demo:index.js文件的简单使用为示例:
// 引入observe函数 与 Watcher 工具类(类似于vue.$Watch())
import observe from './my-reactive/observe'
import Watcher from './my-reactive/Watcher'
let obj = {
a: {
m: {
n: 999
}
},
b: '666',
c:[1,2,3,4,5]
}
// 监听与调用
observe(obj)
new Watcher(obj, 'a.m.n', (val)=>{
console.log("😔", val);
})
// 改变对象和数组
obj.c.splice(2,1,88,99)
obj.a.m.n = 10000
创建oberve侦察函数
import Observer from "./Observer"
// 创建oberve侦察函数
export default function observe(val) {
if (typeof val !== 'object') return
let ob
if (typeof val.__ob__ !== 'undefined') {
ob = val.__ob__
} else {
ob = new Observer(val)
}
return ob
}
Observer类
循环递归对象的属性 将一个正常的obj转换成为每一个层级的属性都是响应式的(都可以被侦测的)obj
import defineReactive from "./defineReactive"
import { arrayMethods } from './array'
import observe from "./observe";
import Dep from "./Dep";
function def(obj, key, value, enumerable) {
Object.defineProperty(obj, key, {
value,
enumerable,
writable: true,
configurable: true,
})
}
export default class Observer {
constructor(value) {
// 每一个Observer的实例上 都会有一个dep
this.dep = new Dep()
// this是Oberver类的实例,不是类本身
def(value, '__ob__', this, false)
// 检查是数组还是对象
if (Array.isArray(value)) {
Object.setPrototypeOf(value, arrayMethods)
// 让这个数组进行observe
this.observeArray(value)
} else {
this.walk(value)
}
}
walk(value) {
for (const k in value) {
defineReactive(value, k)
}
}
observeArray(arr){
for (let i = 0; i < arr.length; i++) {
// 逐项去observe
observe(arr[i])
}
}
}
defineReactive
创建一个可以使用外部变量的闭包函数,包含了Object.defineProperties的核心功能
import observe from "./observe";
import Dep from "./Dep";
export default function defineReactive(data, key, val) {
// 每个层级的__ob__属性上都有一个dep: Dep {}
const dep = new Dep()
if (arguments.length == 2) {
val = data[key]
}
// 子元素要进行observe,至此形成了循环递归
let childOb = observe(val)
Object.defineProperty(data, key, {
// 可被枚举
enumerable: false,
// 一些可配置的属性
configurable: true,
// getter 收集依赖
get() {
if (Dep.target) {
dep.depend()
if (childOb && childOb.length) {
childOb.dep.depend()
}
}
return val
},
// setter 数据劫持
set(newVal) {
if (val === newVal) return
val = newVal
// 当设置了新值,这个值也要被observe
childOb = observe(newVal)
// 观察者模式
dep.notify()
}
})
}
Dep类
使用发布订阅的广播模式,用来管理依赖
dep.depend()
dep.notify()
let uid = 0
export default class Dep{
constructor(){
// 这个数组里放的是watcher的实例
this.id = uid++
this.subs = []
}
// 添加订阅
addSub(sub){
this.subs.push(sub)
}
// 添加依赖
depend(){
if (Dep.target) {
this.addSub(Dep.target)
}
}
// 通知更新
notify(){
console.log('dep类 notify方法');
// 浅拷贝一份subs
const subs = this.subs.slice()
// 遍历
for (let i = 0; i < subs.length; i++) {
subs[i].update()
}
}
}
watcher类
是一个中介,数据发生变化的时候通过watcher中转,通知组件更新
import Dep from "./Dep"
let uid = 0
// 闭包 函数柯里化 高阶函数
function parsePath(str) {
let strArr = str.split('.')
return (obj) => {
for (let i = 0; i < strArr.length; i++) {
if (!obj) return
obj = obj[strArr[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()
}
get() {
// 进入依赖收集
Dep.target = this
const obj = this.target
let value
try {
value = this.getter(obj)
} catch (error) {
console.log('Watcher 报错:', error);
} finally {
Dep.target = null
}
return value
}
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)
}
}
}
重点:ArrayMethod
- 改写7个方法,原来的方法在Array.prototype上
- vue当中数组的响应侦测是如何实现的?以arrayPrototype为原型,创建了arrayMethords的对象,使用了Object.setPrototypeOf()改写了七个数组的方法
function def(obj, key, value, enumerable) {
Object.defineProperty(obj, key, {
value,
enumerable,
writable: true,
configurable: true,
})
}
// 得到Array的原型对象
const arrayPrototype = Array.prototype
// 以arrayPrototype为原型,创建了arrayMethords的对象, 并暴露
export const arrayMethods = Object.create(arrayPrototype)
// 要改写的7个方法
const methodNeedChange = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'revert'
]
// 遍历 并把改写的方法添加到自己的原型上
methodNeedChange.forEach(item => {
// 备份原来的方法
const OriginalMethod = arrayPrototype[item]
// 定义新的方法
def(arrayMethods, item, function() { // 这里不能用箭头函数,当前this指向的是传入的这个数组本身 [2, 3, 4, 5]
// 恢复原来的功能
const result = OriginalMethod.apply(this, arguments)
// arguments是类数组对象 需要转变为数组 才能使用数组的slice等方法
const args = [...arguments]
let insertArr = []
switch (item) {
case 'push':
case 'unshift':
insertArr = args
break;
case 'splice':
insertArr = args.slice(2)
}
// 让新项也变为可响应的
const ob = this['__ob__']
if (insertArr.length>0) {
ob.observeArray(insertArr)
}
// 让数组也有notify方法
ob.dep.notify()
return result
}, false)
})