您现在的位置是:网站首页> 编程资料编程资料
Vue响应式原理模拟实现原理探究_vue.js_
2023-05-24
374人已围观
简介 Vue响应式原理模拟实现原理探究_vue.js_
前置知识
数据驱动
数据响应式——Vue 最标志性的功能就是其低侵入性的响应式系统。组件状态都是由响应式的 JavaScript 对象组成的。当更改它们时,视图会随即自动更新。
双向绑定——数据改变,视图改变;视图改变,数据也随之改变
数据驱动是Vue最独特的特性之一 —— 开发者只需关注数据本身,而无需关心数据如何渲染到视图
数据响应式的核心原理
Vue 2.x
Document hello
当data中有多个对象时,需要对其进行遍历,此时需要对上述代码进行一些改造。
let data = { msg: 'hello', count: 10 } let vm = {} proxyData(data) function proxyData(data) { Object.keys(data).forEach(key => { Object.defineProperty(vm, key, { enumerable: true, configurable: true, get() { return data[key] }, set(newValue) { if (newValue === data[key]) return data[key] = newValue document.querySelector('#app').textContent = data[key] } }) }) }Vue 3.x
步入Vue3,尤小右使用Proxy对其进行了改造,不仅抛弃了如 $delete 之类的鸡肋API(因为Proxy可以监听删除属性),还提升了性能。
Document hello
发布订阅和观察者模式
发布/订阅模式
注:为简便起见,代码实现并未加入对传参的考虑。
Document
观察者模式
注:为简便起见,代码实现并未加入对传参的考虑。
观察者模式
Vue响应式原理模拟实现

Vue
功能
- 接收初始化参数
- 将data中的数据注入实例并转换成getter/setter
- 调用observer监听data中属性的变化
- 调用compiler解析指令/插值表达式
class Vue { constructor(options) { // 1. 通过属性保存选项的数据 this.$options = options || {} this.$data = options.data || {} this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el // 2. 把data中的数据转换为getter和setter,并注入到Vue实例中 this._proxyData(this.$data) // 3. 调用observer对象,监听数据的变化 new Observer(this.$data) // 4. 调用compiler对象,解析指令和插值表达式 new Compiler(this) } _proxyData(data) { Object.keys(data).forEach(key => { Object.defineProperty(this, key, { enumerable: true, configurable: true, get() { return data[key] }, set(newValue) { if (newValue === data[key]) return data[key] = newValue } }) }) } }Observer对data中的属性进行监听
class Observer { constructor(data) { this.walk(data) } walk(data) { // 1.判断data是否是对象 if (!data || typeof data !== 'object') { return } // 2.遍历data对象的所有属性 Object.keys(data).forEach(key => { this.defineReactive(data, key, data[key]) }) } defineReactive(obj, key, val) { let that = this // 负责收集依赖 let dep = new Dep() // 如果val是对象,会将其内部的对象也变成响应式数据 this.walk(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { // 收集依赖 Dep.target && dep.addSub(Dep.target) return val }, set(newValue) { if (newValue === val) { return } val = newValue that.walk(newValue) // 发送通知 dep.notify() } }) } }Compiler
class Compiler { constructor(vm) { this.el = vm.$el this.vm = vm this.compile(this.el) } // 编译模板,处理文本节点和元素节点 compile(el) { let childNodes = el.childNodes Array.from(childNodes).forEach(node => { // 处理文本节点 if (this.isTextNode(node)) { this.compileText(node) } else if (this.isElementNode(node)) { // 处理元素节点 this.comipleElement(node) } // 判断node节点是否有子节点 if (node.childNodes && node.childNodes.length) { this.compile(node) } }) } // 编译元素节点,处理指令 comipleElement(node) { Array.from(node.attributes).forEach(attr => { let attrName = attr.name if (this.isDirective(attrName)) { // v-text => text attrName = attrName.substr(2) let key = attr.value this.update(node, key, attrName) } }) } update(node, key, attrName) { let updateFn = this[attrName + 'Updater'] updateFn && updateFn.call(this, node, this.vm[key], key) } // 处理 v-text 指令 textUpdater(node, value, key) { node.textContent = value new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) } // v-model modelUpdater(node, value, key) { node.value = value new Watcher(this.vm, key, (newValue) => { node.value = newValue }) // 双向绑定 node.addEventListener('input', () => { this.vm[key] = node.value }) } // 编译文本节点,处理插值表达式 compileText(node) { // console.dir(node); let reg = /\{\{(.+?)\}\}/ let value = node.textContent if (reg.test(value)) { let key = RegExp.$1.trim() node.textContent = value.replace(reg, this.vm[key]) // 创建watcher对象,当数据改变更新视图 new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) } } // 判断元素属性是否是指令 isDirective(attrName) { return attrName.startsWith('v-') } // 判断节点是否是文本节点 isTextNode(node) { return node.nodeType === 3 } // 判断节点是否是元素节点 isElementNode(node) { return node.nodeType === 1 } }Watcher
class Watcher { constructor(vm, key, cb) { this.vm = vm // data中的属性名称 this.key = key // 回调函数负责更新视图 this.cb = cb // 把watcher对象记录到Dep类的静态属性target Dep.target = this // 触发get方法,在get方法中会调用addSub this.oldValue = vm[key] Dep.target = null } // 当数据发生变化的时候更新视图 update() { let newValue = this.vm[this.key] if (this.oldValue === newValue) { return } this.cb(newValue) } }Dep
class Dep { constructor() { this.subs = [] } // 添加观察者 addSub(sub) { if (sub && sub.update) { this.subs.push(sub) } } // 发送通知 notify() { this.subs.forEach(sub => { sub.update() }) } }测试代码
Mini Vue 差值表达式
{{ msg }}
{{ count }}
v-text
v-model
提示: 本文由神整理自网络,如有侵权请联系本站删除!
本站声明:
1、本站所有资源均来源于互联网,不保证100%完整、不提供任何技术支持;
2、本站所发布的文章以及附件仅限用于学习和研究目的;不得将用于商业或者非法用途;否则由此产生的法律后果,本站概不负责!
相关内容
- Node.js 操作本地文件及深入了解fs内置模块_node.js_
- 微信小程序实现扫雷游戏_javascript技巧_
- Vue表单快速上手_vue.js_
- vue项目如何引入element ui、iview和echarts_vue.js_
- Node处理CPU密集型任务有哪些方法_node.js_
- 如何使用 Node.js 将 MongoDB 连接到您的应用程序_node.js_
- 一文理解Redux及其工作原理_React_
- React项目中使用Redux的 react-redux_React_
- Vue.js 状态管理及 SSR解析_vue.js_
- Vue.js 前端路由和异步组件介绍_vue.js_
点击排行
本栏推荐
