- 实现一个数据监听器Observer,通过递归遍历对象,给对象上所有的属性,包括现有的和新添加的属性与子属性,都加上 setter和getter。这个对象的某个值赋值,就会触发setter,那么就能监听到了数据的变化拿到最新值并通知订阅者。
- 实现一个指令解析器Compile,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图。
- 实现一个Watcher,作为连接Observer和Compile的桥梁。主要做的事情是:
- 在自身实例化时往属性订阅器(dep)里面添加自己。
- 自身必须有一个update()方法。
- 待属性变动dep.notify()通知时,能调用自身的update()方法,并触发Compile中绑定的回调。
- MVVM作为数据绑定的入口,整合Observer、Compile、Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定。
<!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">
<input type="text" v-model='name'>
<p class="ss">{{name}}</p>
<p>{{obj.a}}</p>
</div>
</body>
</html>
<script>
class MVVM {
constructor(options) {
let data = this.$data = options.data
this.$el = options.el
//将this代理到this.$data
Object.keys(data).forEach(key => {
Object.defineProperty(this, key, {
enumerable: true,
configurable: false,
get() {
return this.$data[key]
},
set(newVal) {
this.$data[key] = newVal
}
})
})
//数据劫持
observe(data)
//指令解析器Compile
new Compile(this.$el, this)
}
}
function observe(data) {
if (!data || typeof data !== 'object') return
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
function defineReactive(data, key, value) {
let dep = new Dep()
//可能data.obj 还是个对象,继续劫持
observe(value)
Object.defineProperty(data, key, {
enumerable: true,
configurable: false,
get() {
//把watcher添加到Dep中
Dep.target && dep.addSub(Dep.target)
return value
},
set(newVal) {
if (newVal === value) return
value = newVal
//劫持新的对象
observe(newVal)
//下发通知
dep.notify()
}
})
}
class Compile {
constructor(el, vm) {
this.$el = document.querySelector(el)
this.vm = vm
//创建文档碎片
this.$fragment = this.createFragement(this.$el)
//编译模板
this.init(this.$fragment)
//返回渲染
this.$el.appendChild(this.$fragment)
}
createFragement(dom) {
let fragment = document.createDocumentFragment()
let child
while (child = dom.firstChild) {
fragment.appendChild(child);
}
return fragment
}
init(fragment) {
let childNodes = fragment.childNodes;
Array.from(childNodes).forEach(node => {
let text = node.textContent
let reg = /\{\{(.*)\}\}/;
if (node.nodeType === 1) {
//元素节点 v-mode
this.compileElement(node)
} else if (node.nodeType === 3 && reg.test(text)) {
//文本节点 {{}}
this.compileText(node, RegExp.$1)
}
//循环遍历子节点
if (node.childNodes) {
this.init(node)
}
})
}
compileElement(node) {
let attrs = node.attributes
// console.log(Array.from(attrs));//[type,v-model]
Array.from(attrs).forEach(attr => {
let name = attr.name
let val = attr.value // v-model='name' 取出name
if (name.indexOf('v-') === 0) {
node.value = this.vm[val];
}
new Watcher(this.vm, val, newVal => node.value = newVal)
node.addEventListener('input', (e) => {
let value = e.target.value
this.vm[val] = value //给input框设置值 则会调用set。触发Watcher
})
})
}
compileText(node, exp) {
//exp: name obj.a
let arr = exp.split('.')
let text = arr.reduce((pre, cur) => {
return pre[cur]
}, this.vm)
new Watcher(this.vm, exp, newVal => node.textContent = newVal)
node.textContent = text
}
}
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
notify() {
this.subs.forEach(sub => sub.updata())
}
}
function Watcher(vm, exp, cb) {
this.vm = vm
this.exp = exp
this.cb = cb
Dep.target = this
//触发get
let val = vm
let arr = exp.split('.')
arr.forEach(key => {
val = this.vm[key]
})
Dep.target = null
}
Watcher.prototype.updata = function () {
let arr = this.exp.split('.')
let text = arr.reduce((pre, cur) => {
return pre[cur]
}, this.vm)
this.cb(text)
}
let vm = new MVVM({
el: '#app',
data: {
name: 'xgw',
obj: {
a: '二级'
}
}
})
</script>