数据驱动
-
数据响应式
数据模型仅仅是普通的 JavaScript 对象,而当我们修改数据时,视图会进行更新,避免了繁 琐的 DOM 操作,提高开发效率
-
双向绑定
数据改变,视图改变;视图改变,数据也随之改变
-
数据驱动
Vue 最独特的特性之一,开发过程中仅需要关注数据本身,不需要关心数据是如何渲染到视图
数据响应式核心原理
-
使用Object.defineProperty(),将data中的property转为getter/setter,从而实现响应式
// 模拟 Vue 中的 data 选项 let data = { msg: 'hello' } // 模拟 Vue 的实例 let vm = {} // 数据劫持:当访问或者设置vm中的成员时,做一些干预操作 Object.defineProperty(vm, 'msg', { // 可枚举 enumrable: true, // 可配置(可以使用 delete 删除,可以通过 defineProperty 重新定义) configurable: true, // getter get () { console.log('get:', data.msg) return data.msg } // setter set (newValue) { console.log('set:', newValue) if (newValue === data.msg) return data.msg = newValue // 数据更改,更新DOM document.querySelector('#app').textContent = data.msg } }) // 测试 vm.msg = 'Hello World' console.log(vm.msg)
如果一个对象中有多个属性需要设置为响应式时
// 遍历 data 对象的所有属性
Object.keys(data).forEach(key => {
// 把 data 中的属性,转换成 vm 的 setter/setter
Object.defineProperty(vm, key, {
enumerable: true,
configurable: true,
get () {
console.log('get: ', key, data[key])
return data[key]
},
set (newValue) {
console.log('set: ', key, newValue)
if (newValue === data[key]) {
return
}
data[key] = newValue
// 数据更改,更新 DOM 的值
document.querySelector('#app').textContent = data[key]
}
})
})
-
Vue3.0
使用Proxy实现,直接监听对象,而不再是属性,弊端是IE不支持
// 模拟 Vue 中的 data 选项 let data = { msg: 'hello', count: 0 } let vm = new Proxy(data, { // 当访问vm的成员会执行 get (target, key) { console.log('get:', key , target[key]) return target[key] }, // 设置vm成员时执行 set (target, key, newValue) { console.log('set:', key, newValue) if (target[key] === newValue) return target[key] = newValue document.querySelector('#app').textContent = newValue } }) // 测试 vm.msg = 'Hello World' console.log(vm.msg)
发布订阅模式和观察者模式
-
发布订阅模式
我们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信 号,其他任务可以向信号中心"订阅"(subscribe)这个信号,从而知道什么时候自己可以开始执 行。这就叫做"发布/订阅模式"(publish-subscribe pattern)
-
this.$emit('myEvent') this.$on('myEvent', () => { console.log('myEvent') })
模拟实现vue自定义事件
// 事件触发器 class EventEmitter { constructor () { // { 'click': [fn1, fn2], 'change': [fn] } 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]) { this.subs[eventType].forEach(handler => { handler() }) } } } // 测试 let em = new EventEmitter() em.$on('click', () => { console.log('click1') }) em.$on('click', () => { console.log('click2') }) em.$emit('click')
-
兄弟组件通信过程
// 事件中心 eventBus.js let bus = new vue() // 组件A 发布者 fn1 () { bus.$emit('add-todo', { text: this.newTodoText }) } // 组件B 订阅者 created () { bus.$on('add-todo', this.addTodo) }
-
观察者模式
有观察者(订阅者)——Watcher和目标(发布者)——Dep组成,当事件发生时,通过notify通知所有观察者,观察者模式没有事件中心
// 目标(发布者)
class Dep {
constructor() {
// 存储所有观察者
this.subs = []
}
// 添加观察者
addSub(sub) {
if (sub && sub.update) {
this.subs.push(sub)
}
}
// 通知所有观察者
notify() {
this.subs.forEach((sub) => {
sub.update()
})
}
}
// 观察者(订阅者)
class Watcher {
update() {
console.log('update')
}
}
// 测试
let dep = new Dep()
let watcher = new Watcher()
dep.addSub(watcher)
dep.notify()
-
总结
观察者模式是由具体目标调度,比如当事件触发,Dep 就会去调用观察者的方法,所以观察者模式的订阅者与发布者之间是存在依赖的。
发布/订阅模式由统一调度中心调用,因此发布者和订阅者不需要知道对方的存在。