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) } }) }