思路:
1、使用Object.defineProperty()实现
Object.defineProperty:直接在一个对象上定义一个新属性,或者修改一个已经存在的属性
Object.defineProperty(对象名,属性名,{配置项})
2、目标对象中的每一个数据都要监听,所以需要遍历目标对象,操作覆盖所有数据
3、数据和view需要关联,获取所有标签节点后使用attributes得到v-text/v-modal对应的属性名,使用innerHTML(文本)或者value(input)实现M->V,监听input事件实现V->M
4、为了精确更新,将对应的属性和相关操作在V->M时收集起来,下一次修改对应属性时精确更新此属性的相关回调,即把值放入dom中展示的语句
<!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>手写响应式</title>
</head>
<body>
<div id="app">
<div v-text="name"></div>
<input type="text" v-modal="age">
</div>
</body>
</html>
<script>
let data = {
name: '123',
age: 18,
sex: '女'
}
//处理data中每一个数据
Object.keys(data).forEach(prop => {
defineReactive(data, prop, data[prop])
})
function defineReactive(data, prop, value) {
//使用Object.defineProperty实现
Object.defineProperty(data, prop, {
get() {
//get获取值
return value
},
set(newVal) {
//set修改值
if (value === newVal) return //若修改前后值没变 不做操作
value = newVal //更新值
Dep.trigger(prop) //执行收集的使用到当前属性的所有依赖的回调函数
}
})
}
let Dep = {
_dep: {}, //存放属性以及对应的回调函数
collect(event, fn) {
//收集
if (!this._dep[event]) { this._dep[event] = [] }
this._dep[event].push(fn)
},
trigger(event) {
//执行
this._dep[event].forEach(fn => {
fn()
})
}
}
function compile() {
//M->v v-> M 的实现
let childNodes = document.querySelector('#app').childNodes
childNodes.forEach(node => {
if (node.nodeType == '1') {
//nodeType 1 -> 节点类型为标签
let attr = node.attributes
Array.from(attr).forEach(attrItem => {
let nodeName = attrItem.nodeName
let nodeVal = attrItem.nodeValue
if (nodeName === 'v-text') {
node.innerHTML = data[nodeVal]
Dep.collect(nodeVal, () => {
node.innerHTML = data[nodeVal]
})
}
if (nodeName === 'v-modal') {
node.value = data[nodeVal]
Dep.collect(nodeVal, () => {
node.value = data[nodeVal]
})
node.addEventListener('input', (e) => {
data[nodeVal] = e.target.value
})
}
})
}
})
}
compile()
</script>