【Vue2.x源码系列07】监听器watch原理
上一章 Vue2计算属性原理,我们介绍了计算属性是如何实现的?计算属性缓存原理?以及洋葱模型是如何应用的?
本章目标
- 监听器是如何实现的?
- 监听器选项 - immediate、deep 内部实现
初始化
在 Vue初始化实例的过程中,如果用户 options选项中存在侦听器,则初始化侦听器
// 初始化状态
export function initState(vm) {
const opts = vm.$options // 获取所有的选项
// 初始化监听器
if (opts.watch) { initWatch(vm) }
}
类型:{ [key: string]: string | Function | Object | Array }
一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。
监听器初始化时,需要兼容 watch回调的各种类型。底层还是去调用vm.$watch
创建一个监听器watch
function initWatch(vm) {
let watch = vm.$options.watch
for (let key in watch) {
const handler = watch[key] // handler有可能是 (字符串/对象/函数) 或 数组
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
// handler 有可能是字符串、对象、函数
function createWatcher(vm, key, handler) {
let options = {}
// 兼容字符串
if (typeof handler === 'string') {
handler = vm[handler]
}
// 兼容对象
else if (Object.prototype.toString.call(handler) === '[object Object]') {
options = handler
handler = handler.handler
}
return vm.$watch(key, handler, options)
}
vm.$watch
vm.$watch( expOrFn, callback, [options] )
参数:{string | Function} expOrFn
观察 Vue 实例上的一个表达式或者一个函数计算结果的变化。回调函数得到的参数为新值和旧值
我们会创建一个 侦听器watcher,标识user: true
。如果 immediate选项值为 true,则立即执行一次callback回调
注:在 options选项中使用的侦听器,最终也是调用此方法
Vue.prototype.$watch = function (exprOrFn, cb, options = {}) {
options.user = true
// exprOrFn 可能是字符串firstname or 函数()=>vm.firstname
const watcher = new Watcher(this, exprOrFn, options, cb)
// 立即执行
if(options.immediate){
cb.call(this, watcher.value, undefined)
}
}
侦听器Watcher
在初始化Vue实例时,我们会给每个侦听器都创建一个对应watcher(我们称之为侦听器watcher,除此之外还有 渲染watcher 和 计算属性watcher ),他有一个 value 属性用于缓存侦听器观察的表达式的值
默认标识 user: true,用户的,代表侦听器watcher
exprOrFn,需要观察的表达式或者一个函数的计算结果,需要兼容字符串和函数两种类型
cb,侦听器回调函数。当观察的对象发生变化时,会触发dep.notify
派发更新 并 调用 update 方法,然后再 run 方法中调用 cb 回调,其参数为新值和旧值
deep,侦听器选项之一,代表深度监听。我们需要在 get 方法中递归访问每一个子属性,这样就会保证所有子属性都会收集此侦听器watcher,这里我用了一个取巧的方法 - JSON.stringify ,源码在此
class Watcher {
constructor(vm, exprOrFn, options, cb) {
if (typeof exprOrFn === 'string') {
this.getter = function () {
return vm[exprOrFn]
}
} else {
this.getter = exprOrFn // getter意味着调用这个函数可以发生取值操作
}
// 监听器watcher 用到的属性
this.user = options.user // 标识是否是用户自己的watcher
this.deep = options.deep
this.cb = cb
this.value = this.get() // 存储 get返回值
}
get() {
pushTarget(this)
let value = this.getter.call(this.vm)
this.deep && JSON.stringify(value) // 深度监听
popTarget()
return value
}
// 重新渲染
update() {
queueWatcher(this) // 把当前的watcher 暂存起来,异步队列渲染
}
// queueWatcher 内部执行 run 方法
run() {
let oldValue = this.value
let newValue = this.value = this.get()
if (this.user) {
this.cb.call(this.vm, newValue, oldValue)
}
}
}
【Vue2.x源码系列07】监听器watch原理的更多相关文章
- 事件机制-Spring 源码系列(4)
事件机制-Spring 源码系列(4) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProcess ...
- Spring源码系列 — BeanDefinition扩展点
前言 前文介绍了Spring Bean的生命周期,也算是XML IOC系列的完结.但是Spring的博大精深,还有很多盲点需要摸索.整合前面的系列文章,从Resource到BeanDefinition ...
- SpringCloud 源码系列(6)—— 声明式服务调用 Feign
SpringCloud 源码系列(1)-- 注册中心 Eureka(上) SpringCloud 源码系列(2)-- 注册中心 Eureka(中) SpringCloud 源码系列(3)-- 注册中心 ...
- Ioc容器依赖注入-Spring 源码系列(2)
Ioc容器依赖注入-Spring 源码系列(2) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostPr ...
- Ioc容器BeanPostProcessor-Spring 源码系列(3)
Ioc容器BeanPostProcessor-Spring 源码系列(3) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Io ...
- AOP执行增强-Spring 源码系列(5)
AOP增强实现-Spring 源码系列(5) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProc ...
- 大白话Vue源码系列(02):编译器初探
阅读目录 编译器代码藏在哪 Vue.prototype.$mount 构建 AST 的一般过程 Vue 构建的 AST 题接上文,上回书说到,Vue 的编译器模块相对独立且简单,那咱们就从这块入手,先 ...
- 大白话Vue源码系列(03):生成AST
阅读目录 AST 节点定义 标签的正则匹配 解析用到的工具方法 解析开始标签 解析结束标签 解析文本 解析整块 HTML 模板 未提及的细节 本篇探讨 Vue 根据 html 模板片段构建出 AST ...
- 大白话Vue源码系列(03):生成render函数
阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...
- 大白话Vue源码系列(04):生成render函数
阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...
随机推荐
- Pytorch 60实例
1 初识PyTorch¶ 1.1 张量 1.导入pytorch包 In [1]: import torch 2.创建一个未初始化的5x3张量 In [3]: x = torch.empty(5, 3) ...
- C Ⅸ
数组例子:统计个数 #include <stdio.h> int main(void) { int x; int count[10]; int i; ...
- 探究Tomcat
一.什么是Tomcat? 用来装载javaWeb程序,可以称它为Web容器.是一个运行java的网络服务器,底层是Sochet的一个程序,他也是JSP和Servlet的一个容器. 二.什么要用Tomc ...
- python学习记录(一)-基础
交换变量值 a,b = 10,20 print(a,b) #10 20 a,b = b,a print(a,b) #20 10 大字符串 str = '''最近在看的动漫: 黑之契约者.咒术回战... ...
- 【javascript】关于 canvas.toDataURL()
在工作中遇到了奇怪的问题,在此记录. 一.定义 canvas.toDataURL()方法是返回一个包含图片展示的 数据URL.可以使用 type 参数其类型,默认为 PNG格式,图片的分辨率为96dp ...
- UIPath踩坑记一开发环境检查
第一步:设置--设计--关闭新项目使用新式体验 第二步:Uipath与浏览器的通信护展是否已安装,如果没有安装需要点击安装 第三步:浏览器中安装的扩展是否已经打开
- [Unity框架]资源管理02:热更新
这里可以分成资源打包.资源更新下载.资源加载卸载3个部分 一.资源打包 参考链接: https://blog.uwa4d.com/archives/TechSharing_59.html https: ...
- linux内存管理之malloc、kmalloc、kzalloc、vmalloc的区别
1 用户空间申请内存的方式 1.1 函数原型 void *malloc(unsigned int num_bytes): 1.2 函数解释和说明 如果分配成功则返回指向被分配内存的指针(此存储区中的初 ...
- 在jsp页面int和String类型的相互转换
浅浅地来做一个对比吧! .java文件 int转成string类型:String s=String.valueOf(int m); String转成int类型:int m=Integer.parseI ...
- 4.0 SDK Workshop 纪实:一起体验多人、多屏幕共享新功能
在本月初,声网发布了 RTC Native SDK 4.0 版本.该版本提供了更高的开发灵活度,可明显提升实时场景开发效率,并让第三方插件开发更容易.上周六(8月20日),我们组织了一场小型的线下 W ...