数据驱动器
- 数据响应
- 数据模型只是一个普通的JavaScript对象,当我们改变数据时,视图也会更新,避免了繁琐的DOM操作,提高了开发效率
- 双向绑定
- 当数据改变时,显示也改变;当视图改变时,数据也会改变
- 我们可以使用
v-model
在表单元素上创建双向数据绑定
- 数据驱动是Vue最独特的功能之一
- 开发过程中,需要关注数据本身,而不需要关心数据在视图中如何渲染
数据响应原理Vue2
cn.vuejs.org/v2/guide/re…
数据响应原理Vue3
- developer.mozilla.org/zh-CN/docs/…
- 从零开始实现Vue3.0响应式源码www.bilibili.com/video/BV1AQ…
观察者模式和订阅发布者模式
发布订阅模式 – 发布订阅模式
订阅者、发布者、信号中心
// 简单实现
// 事件触发器
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')
观察者模式
- 观察者(订阅者)——观察者
- update():当事件发生时,具体做什么
- Mål(出版商)– 部门
- 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()
})
}
}
// 订阅者-观察者
class Watcher {
update () {
console.log('update')
}
}
// 测试
let dep = new Dep()
let watcher = new Watcher()
dep.addSub(watcher)
dep.notify()
分析
vue.js
* 功能
* 负责接收初始化的参数(选项)
* 负责把 `data` 中的属性注入到 `Vue` 实例,转换成 `getter/setter`
* 负责调用 `observer` 监听 `data` 中所有属性的变化
* 负责调用 `compiler` 解析指/差值表达式
* 结构
* 属性
* $options
* $el
* $data
* 方法
* _proxyData()
observe.js
* 功能
* 负责把 `data` 选项中的属性转换成响应式数据
* `data` 中的某个属性也是对象,把该属性转换成响应式数据
* 数据变化发送通知
* 结构
* 属性
* 无
* 方法
* walk(data)
* defineReactive(data, key, value)
class Observer {
constructor(data) {
this.walk(data)
}
walk(data) {
// 1. 判断data是否是对象
if (!data || typeof data != 'object') {
return
}
// 2. 遍历data对象的所有属性
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
defineReactive(obj, key, val) {
let that = this
// 如果val是对象,把val内部的属性转换成响应式数据
this.walk(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get () {
return val
},
set (newValue) {
if (newValue == val) {
return
}
val = newValue
that.walk(newValue)
// 发送通知
}
})
}
}
编译器.js
- 功能
- 负责编译模板、解析指令/差异表达式
- 负责页面的首次渲染
- 当数据更改时渲染视图
- 结构
- 特性
- 或
- vm
- 方法
- 编译(el)
- 编译元素(节点)
- 编译文本(节点)
- isDirective(attrName)
- 是TextNode(节点)
- isElementNode(节点)
- 特性
class Compiler {
constructor(vm) {
this.el = vm.$el
this.vm = vm
this.compile(this.el)
}
// 编译模板,处理文本节点和元素节点
compile(el) {
let childNodes = el.childNodes
Array.from(childNodes).forEach(node => {
// 处理文本节点
if (this.isTextNode(node)) {
this.compileText(node)
} else if (this.isElementNode(node)) {
// 处理元素节点
this.compileElement(node)
}
if (node.childNodes && node.childNodes.length) {
this.compile(node)
}
})
}
// 编译元素节点,处理指令
compileElement(node) {
// 遍历所有的属性节点
Array.from(node.attributes).forEach(attr => {
// 判断是否是指令
let attrName = attr.name
if (this.isDirective(attrName)) {
// 将 v-text --> text
attrName = attrName.substr(2)
let key = attr.value
this.update(node, key, attrName)
}
})
}
update(node, key, attrName) {
let updateFn = this[attrName + 'Updater']
updateFn && updateFn(node, this.vm[key])
}
// 处理 v-text 指令
textUpdater(node, value) {
node.textContent = value
}
// 处理 v-model
modelUpdater(node, value) {
node.value = value
}
// 编译文本节点,处理差值表达式
compileText(node) {
let reg = /\{\{(.+?)\}\}/
let value = node.textContent
if (reg.test(value)) {
let key = RegExp.$1.trim()
node.textContent = value.replace(reg, this.vm[key])
}
}
// 判断元素属性是否是指令
isDirective(attrName) {
return attrName.startsWith('v-')
}
// 判断节点是否是文本节点
isTextNode(node) {
return node.nodeType == 3
}
// 判断节点是否是元素节点
isElementNode(node) {
return node.nodeType == 1
}
}
dep.js
class Dep {
constructor() {
// 存储所有的观察者
this.subs = []
}
// 添加观察者
addSub(sub) {
if (sub && sub.update) {
this.subs.push(sub)
}
}
// 发送通知
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
// observer.js
defineReactive(obj, key, val) {
let that = this
// 负责收集依赖,并发送通知
let dep = new Dep()
// ...
Object.defineProperty(obj, key, {
get() {
// ...
// 收集依赖
Dep.target && Dep.addSub(Dep.target)
return val
},
set(newValue) {
// ...
// 发送通知
dep.notify()
}
})
}
watcher.js
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
// key 是data中的属性名称
this.key = key;
// 回调函数负责更新视图
this.cb = cb
// 把watcher对象记录到Dep类的静态属性target
Dep.target = this
// 触发get方法,在get方法中会调用addSub
this.oldValue = vm[key]
Dep.target = null
}
// 当数据发生变化 的时候更新视图
update () {
let newValue = this.vm[this.key]
if (this.oldValue == newValue) {
return
}
this.cb(newValue)
}
}
watcher 必须收集在编译器中
compileText
// 编译文本节点,处理差值表达式
compileText(node) {
let reg = /\{\{(.+?)\}\}/
let value = node.textContent
if (reg.test(value)) {
let key = RegExp.$1.trim()
node.textContent = value.replace(reg, this.vm[key])
// 创建 watcher
new Watcher(this.vm, key, (newVal) => {
node.textContent = newVal
})
}
}
textUpdater
和modelUpdater
// 处理 v-text 指令
textUpdater(node, value, key) {
node.textContent = value
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue
})
}
// 处理 v-model
modelUpdater(node, value, key) {
node.value = value
new Watcher(this.vm, key, (newValue) => {
node.value = newValue
})
}
双向绑定
// 处理 v-model
modelUpdater(node, value, key) {
node.value = value
// ...
// 双向绑定
node.addEventListener('input', () => {
this.vm[key] = node.value
})
}
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。