简述
这的发布订阅流程:
1.当我们转换mastach语法的时候 有两个步骤 查找和替换
查找到节点内容中带有{{}}的字符串 解析成数组 然后再vm[xxx]找到对应的值
查找完毕开始替换内容 这是一个查找替换过程
2.单单只有查找替换还不够 因为要双向绑定 数据变化视图变化
此时就需要一个监听 也就是watcher
那么这里就有几个问题 1.什么时候添加订阅 2.什么时候发布 3.怎么添加订阅
添加订阅的目的就是将mastach转换为数据那一步先订阅进dep中然后进行监听统一管理
所以添加订阅要在compile的字符串转化为data后
发布的话肯定是要当我们改变了data中的数据时触发 当我们改变data中的数据时
肯定就会触发set函数 然后此时发布订阅 通知watcher进行更新 看修改了那个值然后进行一个
函数回调 将新值进行替换 这么一个流程
所以显而易见 watcher里的回调肯定就是一个替换新值的过程了
最麻烦的就是新值我们要怎么获取
这里的思路就是:
a.当我们new Watcher时候会触发构造函数 在compile中当我们遍历节点的时候 一次又一次
new Watcher 就会触发构造函数 并且传入vm 以及匹配到的mastach内的变量字符串
b.此时我们就可以通过vm和变量字符串访问data中的属性 访问属性的同时会触发get
然后我们就可以在这里添加订阅了 添加完后当我们修改属性时候就会触发set 此时就可以 触发notify
触发notify就会调用watcher的update 在updata里面我们就可以将之前添加的节点全部进行一次重更新
一次次访问data属性并将新值传入到回调函数中 在回调函数中就可以将新的值进行替换
此时就完成了数据到视图的一个更新
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">
<p>
这个人的年龄{{aaa.age}}
<span>
性别是{{aaa.sex}}
<p>年龄是{{aaa.age}}</p>
</span>
</p>
<h1>这个人的年龄{{aaa.sex}}</h1>
<div>这个人的年龄{{aaa.children.name}}</div>
<i>这个人的年龄{{aaa.children.age}}</i>
这个人的性别{{aaa.sex}}
<div>{{aaa.hobby}}</div>
</div>
<script src="vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
aaa: {
age: 14,
sex: 'aa',
children: {
name: 'son',
age: 18
},
hobby: '123546'
}
}
})
// var obj = {
// age: 14,
// sex: 'aa',
// children: {
// name: 'son',
// age: 18
// },
// hobby: '111'
// }
// var app = document.querySelector('#app')
// var fragment = document.createDocumentFragment()
// var children = app.childNodes
// console.log(children)
// var reg = /\{\{(.*)\}\}/
// var arrChildren = Array.from(children)
// function findMastach(childrenNodes, obj) {
// childrenNodes.forEach(val => {
// var text = val.textContent
// // 判断节点类型 是否符合{{}}
// if (val.nodeType === 3 && reg.test(text)) {
// var newObj = { obj }
// RegExp.$1.split('.').forEach(val => {
// // newObj[val]
// newObj = newObj[val]
// })
// text = text.replace(reg, newObj)
// val.textContent = text
// }
// // 判断是否有子节点 有递归筛选
// if (val.childNodes) {
// findMastach(val.childNodes, obj)
// }
// })
// }
// findMastach(children, obj)
</script>
</body>
</html>
vue.js
function Vue(options) {
// 数据初始化
this.$data = options.data
this.$el = options.el
console.log(this.$el)
// 数据劫持
new Observe(this.$data)
// 数据代理
dataAgancy.call(this, this.$data)
// 编译模板
new Compile(this.$el, this)
}
// 数据劫持
function Observe(data) {
walk(data)
}
// 将对象的属性改为get和set的访问属性
function walk(obj) {
for (const k in obj) {
let value = obj[k]
// 进来的data第一层属性全部替换为get和set的访问属性
let dep = new Dep()
console.log(dep.subs)
Object.defineProperty(obj, k, {
enumerable: true,
configurable: true,
get() {
Dep.target && dep.addSub(Dep.target)
return value
},
set(newValue) {
console.log('set')
// 监听新值为对象进行递归
walk(newValue)
console.log('修改')
value = newValue
dep.notify()
}
})
console.log(dep)
// 判断第二层的属性是否为对象
if (value instanceof Object) {
// 为对象进行深度递归
walk(value)
}
}
}
// 数据代理 让vm直接通过定义的属性名 vm.name 访问到值
// 不用vm.$data.name访问
function dataAgancy() {
// 遍历劫持完成后的对象 当前实例的对象为name对象
for (const k in this.$data) {
// this指向的是vm实例 像vm实例中直接添加name属性
Object.defineProperty(this, k, {
enumerable: true,
configurable: true,
get() {
return this.$data[k]
},
set(newValue) {
console.log('代理')
this.$data[k] = newValue
}
})
}
}
// 模板编译
function Compile(el, vm) {
console.log(vm.aaa)
const app = document.querySelector(el)
// 创建一个文档
let fragment = document.createDocumentFragment()
// 将所有子节点放入内存中
while ((child = app.firstChild)) {
fragment.appendChild(child)
}
// 正则检测mastach语法
console.log(fragment)
// 转换为数组
findMastach(fragment.childNodes, vm)
app.appendChild(fragment)
}
// 将dom节点中的{{}}语法挑选出来改换成实际数据
function findMastach(childeNodes, vm) {
const reg = /\{\{(.*)\}\}/
Array.from(childeNodes).forEach(val => {
let text = val.textContent
// 判断节点类型 并 检测是否含有mastach语法
if (val.nodeType === 3 && reg.test(text)) {
// 有
let value = vm
RegExp.$1.split('.').forEach(val => {
value = value[val]
})
new Watcher(vm, RegExp.$1, function(newValue) {
val.textContent = text.replace(reg, newValue)
})
val.textContent = text.replace(reg, value)
}
// 无
// 判断子节点 子节点类型可能为3也可能为1
// 如果为3 继续上述检测mastach语法
// 如果为1 继续递归判断
if (val.childNodes) {
findMastach(val.childNodes, vm)
}
})
}
// 发布订阅模式
function Dep() {
this.subs = []
}
Dep.prototype.addSub = function(sub) {
this.subs.push(sub)
}
Dep.prototype.notify = function() {
this.subs.forEach(val => {
val.update()
})
}
function Watcher(vm, exp, fn) {
this.fn = fn
this.vm = vm
this.exp = exp
Dep.target = this
let arr = exp.split('.')
let value = vm
arr.forEach(val => {
value = value[val]
})
Dep.target = null
}
Watcher.prototype.update = function() {
arr = this.exp.split('.')
console.log(arr)
let value = this.vm
arr.forEach(val => {
value = value[val]
})
console.log(value)
this.fn(value)
}
// 这的发布订阅流程:
// 1.当我们转换mastach语法的时候 有两个步骤 查找和替换
// 查找到节点内容中带有{{}}的字符串 解析成数组 然后再vm[xxx]找到对应的值
// 查找完毕开始替换内容 这是一个查找替换过程
// 2.单单只有查找替换还不够 因为要双向绑定 数据变化视图变化
// 此时就需要一个监听 也就是watcher
// 那么这里就有几个问题 1.什么时候添加订阅 2.什么时候发布 3.怎么添加订阅
// 添加订阅的目的就是将mastach转换为数据那一步先订阅进dep中然后进行监听统一管理
// 所以添加订阅要在compile的字符串转化为data后
// 发布的话肯定是要当我们改变了data中的数据时触发 当我们改变data中的数据时
// 肯定就会触发set函数 然后此时发布订阅 通知watcher进行更新 看修改了那个值然后进行一个
// 函数回调 将新值进行替换 这么一个流程
// 所以显而易见 watcher里的回调肯定就是一个替换新值的过程了
// 最麻烦的就是新值我们要怎么获取
// 这里的思路就是:
// a.当我们new Watcher时候会触发构造函数 在compile中当我们遍历节点的时候 一次又一次
// new Watcher 就会触发构造函数 并且传入vm 以及匹配到的mastach内的变量字符串
// b.此时我们就可以通过vm和变量字符串访问data中的属性 访问属性的同时会触发get
// 然后我们就可以在这里添加订阅了 添加完后当我们修改属性时候就会触发set 此时就可以 触发notify
// 触发notify就会调用watcher的update 在updata里面我们就可以将之前添加的节点全部进行一次重更新
// 一次次访问data属性并将新值传入到回调函数中 在回调函数中就可以将新的值进行替换
// 此时就完成了数据到视图的一个更新