update关联一个视图的时候特别慢_实现一个简单的Vue.js
原文转自 https://const_white.gitee.io/gitee-blog/blog/vue/mini-vue/
Vue响应式原理
图片引自 孟思行 - 图解 Vue 响应式原理
乞丐版 mini-vue
实现mini-vue
之前,先看看官网的描述。在Vue
官网,深入响应式原理中,是这样说明的:
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
起步
技术原因,这里不做
Virtual DOM
、render
部分,而选择直接操作DOM
简单来说,mini vue
在创建Vue
实例时
Vue
类负责把data
中的属性注入到Vue
实例,并调用Observer
类和Compiler
类。Observer
类负责数据劫持,把每一个data
转换成getter
和setter
。其核心原理是通过Object.defineProperty
实现。Compiler
类负责解析指令和插值表达式(更新视图的方法)。Dep
类负责收集依赖、添加观察者模式。通知data
对应的所有观察者Watcher
来更新视图。在Observer
类把每一个data
转换成getter
和setter
时,会创建一个Dep
实例,用来负责收集依赖并发送通知。在每一个data
中在getter
中收集依赖。在setter
中通知依赖,既通知所有Watcher
实例新视图。Watcher
类负责数据更新后,使关联视图重新渲染。
实现代码都添加了详细的注释,无毒无害,可放心查看
Vue类
class Vue { constructor(options) { // 1. 保存 options的数据 this.$options = options || {} this.$data = options.data || {} this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el // 2. 为方便调用(vm.msg),把 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类
class Observer { constructor(data) { this.walk(data) } // 遍历 data($data)中的属性,把属性转换成响应式数据 walk(data) { if (!data || typeof data !== 'object') { return } Object.keys(data).forEach((key) => { this.defineReactive(data, key, data[key]) }) } // 定义响应式数据 defineReactive(obj, key, value) { const that = this // 负责收集依赖并发送通知 let dep = new Dep() // 利用递归使深层(内部)属性转换成响应式数据 this.walk(value) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get() { // 收集依赖 Dep.target && dep.addSub(Dep.target) return value }, set(newValue) { if (value === newValue) { return } value = newValue // 如果新设置的值为对象,也转换成响应式数据 that.walk(newValue) // 发送通知 dep.notify() } }) }}
Compiler类
class Compiler { constructor(vm) { this.vm = vm this.el = vm.$el this.compiler(this.el) }
// 编译模板,处理文本节点和元素节点 compiler(el) { const childNodes = el.childNodes Array.from(childNodes).forEach(node => { // 处理文本节点 if (this.isTextNode(node)) { this.compilerText(node) } else if (this.isElementNode(node)) { // 处理元素节点 this.compilerElement(node) }
// 判断 node节点是否有子节点。如果有,递归调用 compile if (node.childNodes.length) { this.compiler(node) } }) }
// 编译元素节点,处理指令 compilerElement(node) { // 遍历所有属性节点 Array.from(node.attributes).forEach(attr => { // 判断是否 v-开头指令 let attrName = attr.name if (this.isDirective(attrName)) { // 为了更优雅的处理不同方法,减去指令中的 v- attrName = attrName.substr(2) const 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
// 创建 Watcher对象,当数据改变时更新视图 new Watcher(this.vm, key, (newValue) => { node.textContent = newValue }) }
// 处理 v-model指令 modelUpdater(node, value, key) { node.value = value
// 创建 Watcher对象,当数据改变时更新视图 new Watcher(this.vm, key, (newValue) => { node.value = newValue })
// 双向绑定 node.addEventListener('input', () => { this.vm[key] = node.value }) }
// 编译文本节点,处理插值表达式 compilerText(node) { const reg = /\{\{(.+?)\}\}/ let value = node.textContent if (reg.test(value)) { // 只考虑一层的对象,如 data.msg = 'hello world',不考虑嵌套的对象。且假设只有一个插值表达式。 const 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 }}
Dep类
class Dep { constructor() { this.subs = [] } // 添加观察者 addSub(sub) { if (sub && sub.update) { this.subs.push(sub) } } // 发送通知 notify() { this.subs.forEach(sub => { sub.update() }) }}
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() { const newValue = this.vm[this.key] // 数据没有发生变化直接返回 if (this.oldValue === newValue) { return } // 更新视图 this.cb(newValue) }
}
完整版思维导图
对于数组的监听
这里直接把数组的每一项都添加上了getter
和setter
,所以vm.items[1] = 'x'
也是响应式的。
Vue
中为什么没这样做呢?参考 为什么vue没有提供对数组属性的监听
update关联一个视图的时候特别慢_实现一个简单的Vue.js相关推荐
- java写一个音乐播放器源码_求一个JAVA音乐播放器的源代码
展开全部 import javax.media.ControllerEvent; import javax.media.ControllerListener; import javax.media.E ...
- 一个软件公司需要多少前端_制作一个小程序商城需要多少钱?开发小程序公司哪家强?...
搭建小程序商城能更好的为商家打造私域粉丝池,形成私域流量,因此,微信小程序自然成为了企业商家的首选. 随着线上的快速发展,微信小程序能力的升级,使小程序也存在多种类型,例如电商类.资讯类.预约类等等, ...
- python另一个程序正在使用此文件_另一个程序正在使用此文件,进程无法访问
1.Network Error (tcp_error) A communication error occurred: "Operation timed out" 看上去是服务器出 ...
- python定义一个类savingaccount表示银行账户_创建一个SavingAccount类
/*5.创建一个SavingAccount类.使用一个static数据成员 annualInterestRate保存每个存款者的年利率.类的每个 对象都包含一个private数据成员savingsBa ...
- 用Vue.js开发一个电影App的前端界面
我们要构建一个什么样的App? 我们大多数人使用在线流媒体服务(如Netflix)观看我们最喜欢的电影或者节目.这篇文章将重点介绍如何通过使用vue.js 2 建立一个类似风格的电影流媒体WEB交互界 ...
- oracle并行parallel update两张表_Oracle并行更新的两种方式(merge/update内联视图)
对于Oracle的两表联合更新的场景(有A.B两表,以A.id=B.id关联,根据B表中的记录更新A表中的相应字段),一般有update内联视图和merge两种方式,下面举例介绍: 创建用例表: cr ...
- 【JetPack】为现有 Android 项目配置视图绑定 ( ViewBinding ) 模块 ( 视图绑定不影响传统布局操作 | 视图绑定类关联 Activity | 视图绑定类本质 )
文章目录 I . 为现有项目配置 视图绑定 ( ViewBinding ) 应用 II . 视图绑定 ( ViewBinding ) 定制 III . 视图绑定 ( ViewBinding ) 对于正 ...
- mysql可以关联视图_Mysql 五: 数据库自关联、视图
怎么判断两张表中的关系是 一对多 还是 多对一 还是 一对一? 表A 中的 一条数据 对应 表B 中的 一条数据, 则为 一对一 . 表A 中的 一条数据 对应 表B 中的 多条数据, 则为 一对多 ...
- Android自定义控件学习(四)------创建一个视图类
创建一个视图类 精心设计的自定义视图与其他精心设计的类非常相似.它使用易于使用的界面封装了一组特定的功能,它可以高效地使用CPU和内存,等等.不过,作为一个设计良好的设计,自定义视图应该: 符合And ...
最新文章
- 找父节点和子节点个数(Poj1634)
- “科创30条”鼓励高校开设AI新兴学科
- UA MATH564 概率论VI 数理统计基础3 卡方分布上
- java中的年轻态,14、Java垃圾回收机制(示例代码)
- [Spring5]IOC容器_Bean管理注解方式_注入属性@Autowired_@Qualified_@Resource_@Value
- 大数据之-Hadoop3.x_MapReduce_MapTask源码解析---大数据之hadoop3.x工作笔记0126
- @IT老司机 6月3日,扫除技术与产品选型难题,CSDN选型智囊团来了!
- 【数据分析师自学系列】Kettle下载安装、Kettle环境部署
- quartusII编译时出现Error (119013): Current license file does not support the EP4CE6F17C8 device
- Log4j 漏洞修复检测 附检测工具
- Lotus 新手运维手册
- ADP论文关键要点总结
- 网上订餐叫外卖的发展优势
- 轻松一下:python(turtle模块)绘制分形图
- Nginx rewrite路由重写
- 位于法国诺曼底旅游核心区的275英亩地产将通过Concierge Auctions无保留地拍卖出售
- 芯片验证漫游指南-读书笔记
- Javascript DOM 编程艺术读书笔记16/04/01
- php高级工程简历技能特长怎么写,简历中如何写好个人特长?
- 【流体力学】分享几本流体力学经典教材(持续更新)
热门文章
- Java学习笔记004——接口、克隆、回调、内部类
- 基于交换机的PC端网络通信
- 【剑指offer】面试题15:二进制中1的个数(Java)
- mysql 8.0认证失败_Node.js无法对MySQL 8.0进行身份验证
- 苹果挂端口方法_调音台变身直播声卡的方法
- Java中bytebuffer和string的转换记录
- openvino安装小记
- 怎样在dos窗口中启动mysql服务器_如何在dos命令中启动mysql或sql server 服务器的一些操作...
- mediarecorder添加时间戳_Python脚本实现数据处理(官方实例)和Hive自带时间函数...
- C++常用字符串分割方法