1.响应式的实现(Object.defineProperty)
在javascript中实现数据响应式一般有两种方案, 正好也对应了vue2.x和vue3.x使用的方式
1.对象属性拦截(vue2.x)=>Object.defineProperty
2.对象整体代理(vue3.x) =>Proxy
- 对象属性拦截的实现是通过
Object.defineProperty
定义对象,通过get,set方法实现的
// 栗子
let data = {
name: '我是栗子'
age: 18
}
// 遍历每个属性
// Object.keys()将对象转化为数组形式
Object.keys(data).forEach((key) => {
defineReactive(data, key, data[key])
})
//响应式转化
// 使用闭包特性, 使value变量不被销毁
function defineReactive(data, name, value) {
Object.defineProperty(data, name, {
get() {
return value
}
set(newVal) {
value = newVal
}
})
}
2.指令的实现(v-model)
<div>
<input v-model="name"></input>
</div>
<script>
let data = {
name: '栗子',
age: 18
}
Object.keys(data).forEach((key) => {
defineReactive(data, key, data[key])
})
function defineReactive(data, key, value ) {
Object.defineProperty(data, key, {
get() {
return value
},
set(newVal) {
if(newVal === value) {
return
}
value = newVal
// 直接全部执行存在问题,会导致没有发生更改得数据也进行执行, 可以通过发布订阅模式(自定义事件)进行优化
compile()
}
})
}
function compile() {
let app = document.getElementById('app')
// 拿到app的所有子元素
const nodes = app.childNodes
// 遍历所有子元素
nodes.forEach(node => {
if(node.nodeType === 1) {
// node.attributes找到标签的属性
const attrs = node.attributes
Array.from(attrs).forEach(attr => {
const dirName = attr.nodeName
const dataProp = attr.nodeValue
// v-model
if(dirName === 'v-model') {
// M->V
node.value = data[dataProp]
// V->M
node.addEventListener('input', (e) => {
data[dataProp] = e.target.value
})
}
// v-text
if(dirName === 'v-text') {
node.innerText = data[dataProp]
}
})
}
})
}
// 首次渲染
compile()
</script>
3.简单实现发布订阅模式
简单版发布订阅模式
// dep对象
const dep = {
// map事件对象存在里面
map: Object.create(null),
// 收集事件
collect(dataProp, updateFn) {
// 判断map中是否存在该事件
if(!this.map[dataProp]) {
this.map[dataProp] = []
}
this.map[dataProp].push(updateFn)
},
// 触发事件
trigger(dataProp) {
this.map[dataProp] && this.map[dataProp].forEach(updateFn => {
updateFn()
})
}
}
- 优化指令实现
进行收集事件
function compile() {
let app = document.getElementById('app')
const nodes = app.childNodes
nodes.forEach(node => {
if(node.nodeType === 1) {
const attrs = node.attributes
Array.from(attrs).forEach(attr => {
const dirName = attr.nodeName
const dateProp = attr.nodeValue
if(dirName === 'v-text') {
// 第一次赋值
node.innerText = data[dataProp]
// 收集跟新函数
dep.collect(dateProp, () => {
node.innerText = data[dataProp]
})
}
})
}
})
}
触发收集函数
function defineReactive(data, key, value) {
Object.defineProperty(data, key, {
get() {
return value
},
set(newVal) {
if(newValue === value) return
value = newValue
// 触发对应事件即可
dep.trigger(key)
}
})
}