mini-vue代码已放到github上。
数据驱动
- 数据响应式、双向绑定、数据驱动
- 数据响应式
- 数据模型仅仅是普通的javascript对象,而当我们修改数据时,视图会进行更新,避免了繁琐的DOM操作,提高开发效率。
- 双向绑定
- 数据改变,视图改变,视图改变,数据也随之改变
- 我们可以使用v-model在表单元素上创建双向数据绑定
- 数据驱动时Vue最独特的特性之一
- 开发过程中仅需要关注数据本身,不需要关心数据是如何渲染到视图
vue2.x响应式原理
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。Object.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
下面我们来演示下Object.defineProperty如何使用
<template>
<div id='app'>hello</div>
</template>
<script>
// 模拟vue中的data
let data = {
msg: 'hello'
}
// 模拟vue实例
let vm = {}
// 数据劫持: 当访问或者设置vm中的成员的时候,做一些干预操作
Object.defineProperty(vm, 'msg', {
// 可枚举
enumerable: true,
// 可配置
configurable: true,
// 取值操作
get() {
console.log('get:', data.msg)
return data.msg
},
// 设置值操作
set(newValue) {
if(newValue === data.msg) {
return
}
data.msg = newValue
document.querySelector('#app').textContent = data.msg
}
})
</script>
上述代码我们演示了单个属性的转化方法,如果多个属性的话即需要循环的给属性添加defineProperty即可,这块不做演示了
vue3.x响应式原理
vue3.x中使用proxy来进行监听对象,而不是属性,此外proxy IE不支持。
下面我们通过代码来演示下proxy
// 模拟vue中的data
let data = {
msg: 'hello'
}
// 模拟vue实例
let vm = new Proxy(data, {
// 当访问vm对象的时候执行get方法
get(target, key) {
return target(key)
},
// 当设置vm的成员是会执行set方法
set(target, key, newValue) {
if(target[key] === newValue) {
return
}
target[key] = newValue
document.querySelector('#app').textContent = target[key]
}
})
</script>
发布订阅模式
我们假定存在一个‘信号中心’,某个任务执行完成,就向信号中心发布(publish)一个信号,其他任务可以向信号中心‘订阅(subscibe)’这个信号,从而知道什么时候自己可以开始执行,这就叫做‘发布/订阅模式’(publish-subscribe pattern)
我们用兄弟组件通信过程来演示下发布订阅模式
// eventBus.js
// 事件中心
let eventBus = new Vue();
// ComponentA.vue
// 发布者
addTodo: function() {
// 发布消息
eventBus.$emit('add-todo', {text: '发布新消息了'})
}
// ComponentB.vue
// 订阅者
create: function() {
//订阅消息
eventBus.$on('add-todo', () => {})
}
下面我们来模拟下vue自定义事件
let vm = new Vue();
vm.$on('dataChange', () => {
console.log('dataChange')
})
vm.$emit('dataChange')
class EventEmitter {
constructor() {
this.subs = Object.create(null);
}
$on(eventType, handler) {
this.subs[eventType] = this.subs[eventType] || [];
this.subs[eventType].push(handler)
}
$emit(eventType) {
if(this.subs[eventType] && Array.isArray(this.subs[eventType])) {
this.subs[eventType].forEach(handler => {
handler()
})
}
}
}
观察者模式
- 观察者(订阅者)--Watcher
- update() 当事件发生时,具体要做的事情
- 目标(发布者)--Dep
- subs数组 存储所有观察者
- addSub() 添加观察者
- notify() 当事件发生时,调用所有观察者的update方法
- 没有事件中心
下面我们用一段代码演示观察者模式
// 发布者-目标
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
if(sub && sub.update) {
this.subs.push(sub)
}
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
// 订阅者 Watcher
class Watcher() {
update() {
console.log('update')
}
}
总结:
- 观察者模式是有具体的目标调度,比如事件触发,Dep就会去调用观察者方法,所以观察者模式的订阅者与发布者之间时存在依赖的
- 发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在。