1.实现数据劫持
function observeData(data) {
if (!data || typeof data !== 'object') return
let dependecy = new Dependency()
Object.keys(data).forEach(key => {
let value = data[key];
observeData(value)
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
// getter
get() {
if (Dependency.watcher) {
dependecy.addSub(Dependency.watcher) // 添加一个订阅者
}
return value;
},
// setter
set(newValue) {
value = newValue
observeData(newValue)
dependecy.notify()
}
})
})
}
2.实现Compile函数
// 将模版语法解析成dom
function Compile(el, vm) {
vm.$el = document.querySelector(el)
// 创建文档碎片 并添加节点
const fragment = document.createDocumentFragment()
let child
while (child = vm.$el.firstChild) {
fragment.append(child)
}
CompileElement(fragment)
// 为文档中文本节点添加内容
function CompileElement(node) {
let childNodes = node.childNodes
let reg = /\{\{(.*)\}\}/ // 匹配模版变量
if (node.nodeType === 3) {
// 提前保存nodeValue 否则watcher 回调函数nodeValue为 不带大括号的 匹配不成功
let temp = node.nodeValue
let nodeContent = reg.exec(node.nodeValue) // 双大括号内容键名
if (nodeContent) {
// 若为对象 链式访问属性得到数据
const value = nodeContent[1].split('.').reduce((total, current) => total[current], vm.$data)
node.nodeValue = temp.replace(reg, value)
// 当前值为展示的最新值 所以在此处创建订阅者
new Watcher(vm, nodeContent[1], newValue => {
// node.nodeValue = node.nodeValue.replace(reg, newValue)
node.nodeValue = temp.replace(reg, newValue)
})
}
}
// 递归调用 所有的子节点
childNodes.forEach(child => CompileElement(child))
}
vm.$el.appendChild(fragment)
}
3.实现Dep
class Dependency {
constructor() {
this.subscribe = []
}
addSub(sub) {
this.subscribe.push(sub)
console.log('this.subscribe', this.subscribe);
}
notify() {
this.subscribe.forEach(sub => {
console.log('sub', sub)
sub.update()
}
)
}
}
4.实现Watcher
class Watcher {
constructor(vm, key, callback) {
this.vm = vm
this.key = key
this.callback = callback
Dependency.watcher = this
key.split('.').reduce((total, current) => total[current], vm.$data)
Dependency.watcher = null
}
update() {
const value = this.key.split('.').reduce((total, current) => total[current], this.vm.$data)
this.callback(value)
}
}
5.初始化Vue
class Vue {
constructor(obj_instance) {
this.$el = obj_instance.el;
this.$data = obj_instance.data;
observeData(this.$data)
Compile(this.$el, this)
}
}
6.测试
<!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>
</head>
<body>
<div id="app">
<h1>姓名: {{name}}</h1>
<h2>更多: {{more.like}}</h2>
</div>
</body>
<script src="./minivue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
name: 'siyuyu',
more: {
like: 'song'
}
}
})
</script>
</html>