【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,本篇依然对 ...
随机推荐
- 导入maven包时,非常慢的解决方案
在导入maven包时,很多时候导的很慢,导致找不到包,需要将maven包下载地址替换为aliyun的地址,以下为解决方案 找到使用的maven地址,在文件内添加以下节点然后重启ide即可 <mi ...
- conceive of NIO
设想需要传递数据的两端 输入端与接收端 1.字符存储格式不同,需unicode 2.保存格式不通,需格式化 传输数据时 1.需要介质(通道) 2.需要对数据进行输入端接收端都能识别的格式. 3.需要将 ...
- html:表格
HTML:表格1.表格三标签:(1)<table> 表格(2)<tr> 行(3)<td> 单元格 2.表格的基本语法和结构<table> <t ...
- Spring Boot 配置文件加载位置及优先级
内容摘自http://c.biancheng.net/spring_boot/config-order.html 说明如下: /myBoot:表示 JAR 包所在目录,目录名称自定义: /childD ...
- 【javascript】chormeV8源码阅读之 GC(垃圾回收)过程 笔记
1.为何需要垃圾回收 在V8引擎逐行执行JavaScript代码的过程中,当遇到函数的情况时,会为其创建一个函数执行上下文(Context)环境并添加到调用堆栈的栈顶,函数的作用域(handl ...
- 编写简单的button配合input实现上传文件操作
<template> <button> 导入文件 <input type="file" @change="fileChange" ...
- c# Visual Studio|There is no editor available for ***,make sure the application for the file type(.vb) is installed问题解决方法
这个问题出现在在使用VS编码当中,电脑意外关机,导致的文件的缺失或者损坏. 使用反编译软件(如:ILSpy)对编译后的 .EXE文件进行反编译,在翻遍的结果中将相关代码拷贝至目标路径下,替换所需文件. ...
- nvm use时报错 exit status 1:一堆乱码,exit status 5
如图,nvm list 的时候,在只有一个node版本的时候,也没有指向,表示未启用到这个版本 当使用nvm use 12.13.1的时候,报错 原因是我在之前多次安装node,出错了就删除包,再次安 ...
- win10、win11安装子系统kali linux、图形化界面的安装
1.开启安装Linux子系统需要的扩展 Win+Q搜索功能 勾选需要的扩展,Hyper-V.Windows 虚拟机监控平台.适用于Linux的Windows子系统.虚拟机平台 反正这些有关于虚拟机的全 ...
- sqli-labs搭建
今天使用 phpstudy 搭建了 sqli-labs 练习 SQL 注入平台,其中遇到了两个问题. phpstudy phpstudy 中集成了 Apache.Nginx.PHP.Mysql.php ...