原生js 模拟Vue双向数据绑定原理(详细代码及注释)
2021/7/24 6:11:48
本文主要是介绍原生js 模拟Vue双向数据绑定原理(详细代码及注释),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
数据劫持
先对data中的数据进行劫持并挂载到vue实例上,这时每个数据对象都可以模拟是一个订阅者,当数据发生改变,发布者会通知(调用notify方法)每一个订阅者去调用update方法进行更新,然后通过编译器编译渲染到视图上,当视图发生改变了,每个订阅者会向发布者进行订阅,并返回到进行数据更新,进行数据同步(注释仅个人理解,如有不对,请指教)
class Vue { constructor(options) { this.$options = options; this._data = options.data; this.$el = typeof options.el === "string" ? document.querySelector(options.el) : options.el // 数据劫持 this._proxyData(this._data); // 观察者模式 new Observer(this._data) // 编译 new Complier(this); } _proxyData(data) { Object.keys(this._data).forEach(key => { Object.defineProperty(this, key, { get() { return data[key] }, set(nValue) { if (data[key] === nValue) { return } data[key] = nValue; } }) }) } }
观察者模式
class Observer { constructor(data) { this.walk(data) } // 遍历 walk(data) { // 判断data是否存在,data是不是对象 如果不是或不存在则不处理 // 开始 传入data:{} 是对象 if (!data || typeof data !== "object") { return } // 遍历data中的每一个属性名key 拿到key // object.keys(),将data中的属性名存在一个数组中 [msg,crq,...] Object.keys(data).forEach(key => { // 挂载到vue实例中,对拿到的key值进行代理 key:msg data[key]:"hello vue" this.defineReactive(data, key, data[key]) }) } // 定义响应式数据 将属性属性值挂载到vue实例上 defineReactive(data, key, value) { // 对每一个key进行发布和监听,在这里实例化publisher let publisher = new Publisher(); let that = this; // 调用walk判断传进来的 value 也就是walk中data遍历后的属性值data[key]是不是对象,如果是对象,继续遍历,不是则返回,例如遍历data后key:crq data[key]:{agr:...,sex:...} this.walk(value); // 将属性值挂载到vue实例上 // 这里将key进行代理,为每一个key添加get和set方法 Object.defineProperty(data, key, { get() { // 收集依赖 添加观察者 // publisher.target 就相当于添加的观察者的形参 Publisher.target && publisher.addSub(Publisher.target); return value }, set(nValue) { if (value === nValue) { return } value = nValue // 如果数据修改或新添加数据,重新调用walk判断是否是对象然后进行挂载 that.walk(nValue) // 发布通知,监测到数据发生改变,通知观察者更新数据 publisher.notify() } }) } }
发布者
class Publisher { constructor() { // 这里初始化subs空数组 准备用来储存观察者 this.subs = []; } // 将观察者添加到数组中 addSub(sub) { // 判断 观察者是否存在,并且观察者上是否有更新方法 // 如果有 添加到subs数组中 if (sub && sub.update) { this.subs.push(sub); } } notify() { // 通知每一个观察者 调用update方法更新 console.log("notify"); this.subs.forEach(w => { w.update(); console.log("w.up"); }); } }
订阅者(观察者)
class Wathcher { constructor(data, key, cb) { this.data = data; this.key = key; this.cb = cb Publisher.target = this this.oldValue = data[key] } // 更新视图 // 更新前提是数据一定要发生改变,数据要和之前对比,如果没有变化就不更新 // 获取发布者通知的信息 update() { console.log("watcher--update"); let newValue = this.data[this.key]; // console.log(newValue); if (newValue === this.oldValue) { return } // 如果数据改变,dom更新 this.cb(newValue); } }
编译器
class Complier { constructor(vm) { this.el = vm.$el; this.data = vm._data; this.complie(this.el) } complie(el) { let childNodes = el.childNodes Array.from(childNodes).forEach(node => { // 代码分割 if (this.isTextNode(node)) { // 文本处理 this.complieText(node) } else if (this.isElementNode(node)) { // 元素处理 this.complieElement(node) } // 当元素中包含文本和插值表达式时,需要再调用编译,编译出元素中嵌套的插值表达式和文本 // 判断编译一遍后,里面是否还有节点,如果存在节点则再调用一遍编译 complie if (node.childNodes && node.childNodes.length > 0) { this.complie(node) } }) } // 编译元素 complieElement(node) { // 获取所有属性 let attributes = node.attributes // 遍历所有属性 Array.from(attributes).forEach(attr => { // 获取属性名 // console.log(attr); let attrName = attr.name; // console.log(attrName); // 判断属性是否是 v- 开头 如果是 if (this.isDirective(attrName)) { // 获取v-后面部分 指令名 attrName = attrName.substring(2) console.log(attrName); // 属性值 data[key] let key = attr.value // text - 映射方法,不同指令 不同函数 this.update(node, attrName, key) console.log(this.data[key]); } }) } // 更新 update(node, attrName, key) { // 判断 不同指令调用不同函数 // if (attrName === "text") { // this.textUpdate(node, attrName, key) // } // if (attrName === "model") { // this.modelUpdate(node, attrName, key) // } // fn 为声明的变量, 不能直接调用,要改变this指向 指向complier // 声明fn 代替各种判断 let fn = this[attrName + 'Update']; fn && fn.call(this, node, attrName, key) console.log(key); } // 属性名为 text 时 textUpdate(node, attrName, key) { console.log(key); // 更新节点文本内容 这里this.data[key] node.textContent = this.data[key]; console.log(this.data[key]); // 将数据传入watcher在watcher里面判断,如果数据发生改变,则调用watcher中的update方法,更新视图中数据 new Wathcher(this.data, key, (newValue) => { console.log("txtup"); node.textContent = newValue }) } // 属性名为 model 时 model 实现双向数据绑定 modelUpdate(node, attrName, key) { console.log(key); // console.log(node); // input 里面文本是value node.value = this.data[key] console.log(this.data[key]); // key = node.value // console.log(node.value); new Wathcher(this.data, key, (newValue) => { node.value = newValue }) node.addEventListener('input', () => { this.data[key] = node.value }) } // 编译文本 complieText(node) { // console.log(node); // console.log(node.textContent); let value = node.textContent //内容 let reg = /\{\{(.+?)\}\}/ // 正则规则 // 判断 传入的文本内容中是否有正则表达式,如果有,则获取表达式中的值 if (reg.test(value)) { // 获取:插值表达式的变量名 let k = RegExp.$1.trim() console.log(k); // 替换 node.textContent = value.replace(reg, this.data[k]) // 数据变化 - 通知更新 - update - cb - dom new Wathcher(this.data, k, (newValue) => { node.textContent = newValue }) } } // 节点判断 // 判断属性名是否有v- isDirective(attrName) { // 返回值为boolen return attrName.startsWith("v-") } isTextNode(node) { return node.nodeType === 3 } isElementNode(node) { return node.nodeType === 1 } isAttrNode(node) { return node.nodeType === 2 } }
这篇关于原生js 模拟Vue双向数据绑定原理(详细代码及注释)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-15useCallback教程:React Hook入门与实践
- 2024-11-15React中使用useContext开发:初学者指南
- 2024-11-15拖拽排序js案例详解:新手入门教程
- 2024-11-15React中的自定义Hooks案例详解
- 2024-11-14受控组件项目实战:从零开始打造你的第一个React项目
- 2024-11-14React中useEffect开发入门教程
- 2024-11-14React中的useMemo教程:从入门到实践
- 2024-11-14useReducer开发入门教程:轻松掌握React中的useReducer
- 2024-11-14useRef开发入门教程:轻松掌握React中的useRef用法
- 2024-11-14useState开发:React中的状态管理入门教程