一、如何理解MVVM - Model-View-ViewModel
二、设计模式之 -- 观察者模式/发布订阅模式 【从某种意义上可以视为一样】
1、概念: 定义对象间的一种一对多的依赖关系, 使得每当一个对象状态发生改变,其依赖对象皆得到通知并被自动更新。
2、特点:
- 发布 & 订阅
-
一对n [n可以为1]
观察者模式
// 主题、保存状态,状态变化之后触发所有观察者对象 ---- 【是发布者】
class Subject {
constructor() {
this.state = 0
this.observers = []
}
getState () {
return this.state
}
setState (state) {
this.state = state
this.notifyAllObservers()
}
notifyAllObservers () {
this.observers.forEach(observer => {
observer.update() // 触发事件
})
}
attach (observer) {
this.observers.push(observer)
}
}
// 观察者 --- 订阅目标
class Observer {
constructor(name, subject) {
this.name = name
this.subject = subject
this.subject.attach(this) // 订阅目标
}
update () {
console.log(`${this.name} update, state: ${this.subject.getState()}`)
}
}
const s = new Subject()
const o1 = new Observer('o1', s)
const o2 = new Observer('o2', s)
const o3 = new Observer('o3', s)
s.setState(1)
发布订阅模式
/**
* on 和 once 注册函数,存储起来
*
* emit时找到对应的函数,执行
*
* off找到对应的函数, 从对象中删除
*/
class EventBus {
/**
* {
* key1: [
* { fn: fn1, isOnce: false },
* { fn: fn1, isOnce: false }
* ],
* key2: [] // 有序
* }
*/
constructor() {
this.events = {}
}
on (type, fn, bool = false) {
const events = this.events
if (!events[type]) {
events[type] = []
} else {
events[type].push({fn, isOnce: bool})
}
}
once (type,fn, key) {
this.on(type, fn, true)
}
off (type, fn) {
// const events = this.events
if (!fn) {
this.events[type] = {} // 解绑所有的
} else {
const fnList = this.events[type]
if (fnList) {
this.events[type] = fnList.filter(item => item.fn !== fn)
}
}
}
emit (type, ...args) {
const events = this.events
const fnList = events[type]
console.log(events, fnList)
if (fnList === null) return
events[type] = fnList.filter(item => {
const { fn, isOnce } = item
fn(...args)
// once 执行一次就要被过滤掉
if (!isOnce) { return true }
return false
})
}
}
const e = new EventBus()
function fn1 (a, b) { console.log('fn1', a, b) }
function fn2 (a, b) { console.log('fn2', a, b) }
function fn3 (a, b) { console.log('fn3', a, b) }
e.on('key1', fn1)
e.on('key1', fn2)
e.once('key3', fn3)
e.on('xxxx', fn3)
e.emit('key1', 10, 20) // 触发fn1 fn2 fn3
/*
* e ----- 就是事件处理中心
* emit ---- 发布者
* on --- 订阅者
*/
一、响应式原理 【官网 vs other】
官网的图 data 和 watcher中间少了一个Dep, 图中nodify 和 Collect as DesPendency都是Dep实例做的 【可能是为了方便理解吧, 故意省略Dep类】
Dep是什么东西??
Dep理解
依赖收集器 : 在对data进行响应式的时候, 需要使用 依赖收集器 将所有data的依赖收集起来。Vue中的依赖收集器的具体表现形式就是Dep。
Dep类的具体作用就是在数据get过程中, 收集数据的相关依赖项, 用于之后的更新依赖操作
Dep实例的不会直接存放依赖项,Dep实例存放的其实是各个依赖项对应的watcher【订阅者/观察者】实例,由watcher实例去调用对应依赖项的更新。
<!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>
<script>
/**
* 1、根据上图实现整体一个架构(MVVM类或者VUE类,Watcher类), 这里用到一个发布订阅者模式
* 2、然后实现mvvm中的M -> V,把模型里面的数据绑定到视图
* 3、最后实现V -> M,当文本框输入文本的时候,由文本事件触发更新模型中的数据, 同时也更新相对应的视图
*/
// 发布者
class Vue {
constructor (options) {
this.$data = options.data
this.$el = document.querySelector(options.el); // 获取元素对象
console.log(this.$el, this.$data)
// 容器
// {myText: [订阅者1, 订阅者2], myBox: [订阅者1, 订阅者2] }
// {myText: [Watcher1, Watcher2], myBox: [Watcher1, Watcher2] }
this._directive = {}
this.Observer(this.$data)
this.Complie(this.$el)
}
Observer (data) { // 劫持数据
for (let key in data) {
this._directive[key] = []
let value = data[key]
let watchArr = this._directive[key]
Object.defineProperty(data, key, {
get () {
return value
},
set (newValue) {
if (newValue !== value) {
value = newValue
// item ----> 一个个订阅者的实力对象 , 可以调用其updata方法 更新视图
watchArr.forEach(item => {
item.updata()
});
}
}
})
}
// console.log(this._directive) // {myText: [], myBox: []}
}
// 主要功能: 解析指令
// 为什么需要解析指令???? ----> 依赖收集 ----> 更新视图 ----> 订阅
Complie (el) {
let nodes = el.children; // 获取app div对象下面的所有子对象
for (let i = 0; i< nodes.length; i++ ) {
let node = nodes[i]
if (node.children.length) {
this.Complie(node)
}
if (node.hasAttribute('v-text')) {
// 订阅
// console.log(node.hasAttribute('v-text'))
const attrVal = node.getAttribute('v-text')
// push什么??? ----> 订阅者 ----> 订阅者是谁???
this._directive[attrVal].push(new Watcher(node, this, attrVal, "innerHTML"))
}
if (node.hasAttribute('v-model')) {
// 订阅
const attrVal = node.getAttribute('v-model')
// console.log(node.hasAttribute('v-model'))
this._directive[attrVal].push(new Watcher(node, this, attrVal, "value"))
node.addEventListener('input', () => {
this.$data[attrVal] = node.value
})
}
}
}
}
// 订阅者 ---> 负责更新本身的状态
class Watcher { // 主要功能更新视图
constructor (el, vm, exp, attr) {
this.el = el
this.vm = vm
this.exp = exp
this.attr = attr
this.updata()
}
updata() {
// div.innerHTML = this.vm.$data['myText']
this.el[this.attr] = this.vm.$data[this.exp]
}
}
</script>
</head>
<body>
<div id="app">
<h1>数据响应式</h1>
<div>
<div v-text="myText"></div>
<div v-text="myBox"></div>
<input type="text" v-model="myText">
<input type="text" v-model="myBox">
</div>
</div>
<script>
const app = new Vue({
el: '#app',
data: {
myText: '我是:myText',
myBox: '我是:myBox'
}
})
</script>
</body>
</html>