<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>MVVM</title>
</head>
<body>
<div id="app">
<div>{{name}}</div>
<div>
<p>{{person.age}}</p>
</div>
</div>
</body>
<script>
class Vue {
constructor(options) {
this.el = document.querySelector(options.el)
this.data = options.data
//使得data响应式
new Observer(this.data)
//编译模板
new Compile(this.el, this.data)
}
}
class Observer {
constructor(data) {
if (typeof data !== 'object') {
return
}
this.observe(data)
}
observe(data) {
//循环遍历data属性
Object.keys(data).forEach(key => {
if (typeof data[key] === 'object') {
new Observer(data[key])
} else {
this.defineReactive(data, key)
}
})
}
defineReactive(data, key) {
//dep收集的是watcher 在vue中watcher有三种,分别是渲染、计算、watch属性,三者对应的回调函数不同
//应由一个统一的变量收集,即dep。
//在渲染watcher中,何处收集是我一开始觉得最绕的地方,其实是在编译模板的时候
let dep = new Dep()
let value = data[key]
Object.defineProperty(data, key, {
get() {
//收集watcher 那Dep.target何时为true呢 往下看
if (Dep.target && dep.subs.indexOf(Dep.target) == -1) dep.subs.push(Dep.target)
return value
},
set(newVal) {
if (value === newVal) {
return
}
console.log('set')
value = newVal
new Observer(value)
//调用更新模板的函数
dep.notify()
}
})
}
}
class Compile {
constructor(el, data) {
this.data = data
this.compile(el)
}
compile(el) {
Array.from(el.children).forEach(node => {
if (node.children.length !== 0) {
this.compile(node)
}
if (node.children.length === 0) {
//此处实例化了watcher 先看看watcher的逻辑
new Watcher(node, this.data)
}
})
}
}
class Watcher {
constructor(node, data) {
this.node = node
this.data = data
this.key
//实例化的时候就会调用一个get方法
this.get()
}
update(newVal) {
this.get()
}
get() {
//这里就把Dep的静态属性target设为this,即当前watcher实例
Dep.target = this
let html = this.node.innerHTML
let reg = /\{\{(.*)\}\}/
if (reg.test(html)) {
this.key = reg.exec(this.node.innerHTML)[1]
}
let value = this.data
this.key.split('.').forEach(k => {
value = value[k]
})
//上述获取this.data的某个属性操作中,会触发这个属性的get方法,即把watcher收集到了dep中
//此时完成了依赖收集
this.node.innerHTML = value
Dep.target = null
}
}
class Dep {
constructor() {
this.subs = []
}
notify() {
this.subs.forEach(sub => sub.update())
}
}
Dep.target = null
let app = new Vue({
el: '#app',
data: {
name: 'zj',
person: {
age: 18,
sex: 1
}
}
})
setTimeout(() => {
app.data.name = '123'
setTimeout(() => {
app.data.person.age = 20
}, 500);
console.log(app.data.name)
}, 1000);
</script>
</html>