数据响应式
简单的订阅发布模式
git地址:my-mvvm
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
class Dep {
constructor() {
this.subscribers = new Set()
}
// 添加依赖
depend() {
if (activeUpdate) {
this.subscribers.add(activeUpdate)
}
}
notify() {
// 收集依赖
this.subscribers.forEach(sub => sub())
}
}
function observe(obj) {
Object.keys(obj).forEach(key => {
let internalValue = obj[key]
const dep = new Dep()
Object.defineProperty(obj, key, {
// 在getter收集依赖项,当触发notify时重新运行
get() {
console.log("执行get")
dep.depend()
return internalValue
},
// setter用于调用notify
set(newVal) {
const changed = internalValue !== newVal
internalValue = newVal
if (changed) {
dep.notify()
}
}
})
})
return obj
}
let activeUpdate = null
function autorun(update) {
const wrappedUpdate = () => {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
const state = {
count: 0
}
observe(state)
autorun(() => {
console.log(state.count)
})
// 打印"count is: 0"
state.count = state.count*2; // 打印"count is: 1"
//state.count = 10 //打印10
</script>
</body>
</html>
defineReactive函数监听变化
import observe from './observe'
export default function defineReactive(data, key, val) {
console.log("我是defineReactive", data, key)
if (arguments.length === 2) {
val = data[key]
}
// 子元素要进行observe ,至此形成 递归, 这个递归不是函数自己调用自己,而是多个函数循环、类循环调用
let childOb = observe(val)
Object.defineProperty(data, key, {
// 可枚举
enumerable: true,
// 可被配置,比如delete
get() {
console.log("你试图访问" + key)
return val
},
set(newVal) {
console.log("你试图设置:", data, "的" + key + ",-----newVal", newVal)
if (val === newVal) {
return
}
val = newVal
// 当这个值设置了新值,这个值也要被observe
console.log('childOb', childOb)
childOb = observe(newVal)
}
})
}
工具(添加属性)
export const def = (obj, key, value, enumerable) => {
Object.defineProperty(obj, key, {
value,
enumerable,
writable: true,
configurable: true
})
}
Observer类
- 作用:
将一个正常object转化成一个每个层级的属性都是响应式(可以被侦测的)object
import { def } from './utils'
import defineReactive from './defineReactive'
import { arrayMethods } from './array'
import observe from './observe';
/*
Observer 类
- 作用:
将一个正常object转化成一个每个层级的属性都是响应式(可以被侦测的)object
*/
export default class Observer {
constructor(value) {
// 给实例(this, 一定要注意,构造函数的this不是类本身 ,是实例) 添加了__ob__属性
def(value, '__ob__', this, false)
console.log("我是 Object 对象", value);
// 检查是数组还是对象
if (Array.isArray(value)) {
// 如果是数组,要非常强行的蛮干: 将数组的原型 ,指向arrayMethods
Object.setPrototypeOf(value, arrayMethods);
// 让数组变成observ
this.observeArray(value);
}
this.walk(value)
}
// 遍历
walk(value) {
for (const k in value) {
defineReactive(value, k)
}
}
// 数组的特殊遍历
observeArray(arr) {
for (let index = 0; index < arr.length; index++) {
console.log(arr[index])
// console.log(observe)
observe(arr[index]);
}
}
}
observe函数(绑定Observer类)
import Observer from './Observer'
export default function observe(value) {
// 如果value不是对象则什么多不做
if (typeof value != 'object') return
// 定义ob
let ob;
if (typeof value.__ob__ != 'undefined') {
ob = value.__ob__
} else {
ob = new Observer(value)
console.log('ob', ob)
}
return ob
}
arrayMethods (重写数组方法)
当数组中数据发生变化时,将新增数据也变成响应式数据
import defineReactive from './defineReactive'
import { def } from './utils'
import Observer from './Observer'
/*
改写 7个方法
push
pop
shift
unshift
splice
sort
reverse
*/
const arrayPrototype = Array.prototype;
// 以Array.prototype 为原型创建arrayMethods 对象
export const arrayMethods = Object.create(arrayPrototype);
const methodsNeedChange = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse",
]
methodsNeedChange.forEach(methodsName => {
// 备份原来的方法
const orginal = arrayPrototype[methodsName];
// 定义新的方法
def(arrayMethods, methodsName, function() {
// 恢复原来的功能
const result = orginal.apply(this, arguments)
/*
把数组身上的 __ob__ 取出来, __ob__ 已经被添加 ,为什么被添加了呢,为什么是已经被添加了呢?因为 数组肯定不是最高层
比如obj.arr 属性是数组,obj不能是数组,第一次遍历obj这个对象的第一层的时候,已经给g属性(就是这个数组) 添加了 __ob__属性
*/
const ob = this.__ob__;
// 有三种方法 push \ unshift \ splice 能够插入新的项, 先要把插入的新项 也要变成observe
let inserted = []
const args = [...arguments]
switch (methodsName) {
case 'push':
case 'unshift':
inserted = arguments
break
case 'splice':
// splice格式是splice(下标,数量,插入新项)
inserted = args.slice(2)
break
}
// 判断有没有要插入的新项
if (inserted.length) {
// 重新遍历数据,将数组中新增数据变成响应式
ob.walk(this);
}
return result
}, false)
})
console.log(arrayMethods);
Dep类
let uid = 0;
export default class Dep {
constructor() {
// console.log("我是Dep的构造器")
this.id = uid++;
// 用数组存储自己的订阅者,英语subscribes订阅者的意思
this.subscribers = []
}
// 添加订阅
addSubscriber(subscribe) {
this.subscribers.push(subscribe)
}
// 添加依赖
depend() {
// Dep.target就是一个我们自己指定的全局的位置,用window.target 也行,只要是全局唯一,没有歧义就行
if (Dep.target) {
this.addSubscriber(Dep.target)
}
}
// 通知更新
notify() {
// console.log("我是notify")
// 浅克隆一份
const subscribes = this.subscribers.slice()
// 遍历
for (let i = 0; i < subscribes.length; i++) {
subscribes[i].update()
}
}
}
parsePath使用
let str = "a.b.c.d"
let o = {
a: {
b: {
c: {
d: 23
}
}
}
}
function parsePath(str) {
let segment = str.split('.')
return (obj) => {
for (let i = 0; i < segment.length; i++) {
if (!obj) return
console.log(obj)
obj = obj[segment[i]]
}
return obj
}
}
let fn = parsePath(str)
console.log(fn(o));
Watcher类
import Dep from "./Dep";
let 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.get()
}
update() {
this.run();
}
get() {
// 进入依赖收集阶段 。让全局的Dep.target 设置为Watcher本身那么就是进入到了依赖收集阶段
Dep.target = this;
const obj = this.target;
var value;
try {
value = this.getter(obj)
} 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);
}
}
}
function parsePath(str) {
let segment = str.split('.')
return (obj) => {
for (let i = 0; i < segment.length; i++) {
if (!obj) return
obj = obj[segment[i]]
}
return obj
}
}
defineReactive
import observe from './observe'
import Dep from './Dep.js'
export default function defineReactive(data, key, val) {
const dep = new Dep()
// console.log("我是defineReactive", data, key)
if (arguments.length === 2) {
val = data[key]
}
// 子元素要进行observe ,至此形成 递归, 这个递归不是函数自己调用自己,而是多个函数循环、类循环调用
let childOb = observe(val)
Object.defineProperty(data, key, {
// 可枚举
enumerable: true,
// 可被配置,比如delete
get() {
console.log("你试图访问" + key)
dep.depend();
if (childOb) {
childOb.dep.depend()
}
return val
},
set(newVal) {
console.log("你试图设置:", data, "的" + key + ",-----newVal", newVal)
if (val === newVal) {
return
}
val = newVal
// 当这个值设置了新值,这个值也要被observe
childOb = observe(newVal)
// 发布的订阅模式
dep.notify()
}
})
}
遇到的问题
watcher 收集依赖时多次添加重复依赖,导致监听多次执行
改进方法
使用Set数据结构 添加依赖
let uid = 0;
export default class Dep {
constructor() {
this.id = uid++;
// 用数组存储自己的订阅者,英语subscribes订阅者的意思
this.subscribers = new Set()
}
// 添加订阅
addSubscriber(subscribe) {
this.subscribers.add(subscribe)
}
// 添加依赖
depend() {
console.log(this.subscribers)
// Dep.target就是一个我们自己指定的全局的位置,用window.target 也行,只要是全局唯一,没有歧义就行
if (Dep.target) {
this.addSubscriber(Dep.target)
}
}
// 通知更新
notify() {
// 浅克隆一份
// const subscribes = this.subscribers.slice()
// 遍历
// for (let i = 0; i < this.subscribers; i++) {
// this.subscribers[i].update()
// }
this.subscribers.forEach(sub => {
sub.update();
})
}
}