上一章 Vue2计算属性原理,我们介绍了计算属性是如何实现的?计算属性缓存原理?以及洋葱模型是如何应用的?

本章目标

  • 监听器是如何实现的?
  • 监听器选项 - immediate、deep 内部实现

初始化

在 Vue初始化实例的过程中,如果用户 options选项中存在侦听器,则初始化侦听器

// 初始化状态
export function initState(vm) {
const opts = vm.$options // 获取所有的选项 // 初始化监听器
if (opts.watch) { initWatch(vm) }
}

watch

类型:{ [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原理的更多相关文章

  1. 事件机制-Spring 源码系列(4)

    事件机制-Spring 源码系列(4) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProcess ...

  2. Spring源码系列 — BeanDefinition扩展点

    前言 前文介绍了Spring Bean的生命周期,也算是XML IOC系列的完结.但是Spring的博大精深,还有很多盲点需要摸索.整合前面的系列文章,从Resource到BeanDefinition ...

  3. SpringCloud 源码系列(6)—— 声明式服务调用 Feign

    SpringCloud 源码系列(1)-- 注册中心 Eureka(上) SpringCloud 源码系列(2)-- 注册中心 Eureka(中) SpringCloud 源码系列(3)-- 注册中心 ...

  4. Ioc容器依赖注入-Spring 源码系列(2)

    Ioc容器依赖注入-Spring 源码系列(2) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostPr ...

  5. Ioc容器BeanPostProcessor-Spring 源码系列(3)

    Ioc容器BeanPostProcessor-Spring 源码系列(3) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Io ...

  6. AOP执行增强-Spring 源码系列(5)

    AOP增强实现-Spring 源码系列(5) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProc ...

  7. 大白话Vue源码系列(02):编译器初探

    阅读目录 编译器代码藏在哪 Vue.prototype.$mount 构建 AST 的一般过程 Vue 构建的 AST 题接上文,上回书说到,Vue 的编译器模块相对独立且简单,那咱们就从这块入手,先 ...

  8. 大白话Vue源码系列(03):生成AST

    阅读目录 AST 节点定义 标签的正则匹配 解析用到的工具方法 解析开始标签 解析结束标签 解析文本 解析整块 HTML 模板 未提及的细节 本篇探讨 Vue 根据 html 模板片段构建出 AST ...

  9. 大白话Vue源码系列(03):生成render函数

    阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...

  10. 大白话Vue源码系列(04):生成render函数

    阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...

随机推荐

  1. vue中的普通函数与箭头函数以及this关键字

    普通函数 普通函数指的是用function定义的函数 var hello = function () { console.log("Hello, Fundebug!"); } 箭头 ...

  2. Learning under Concept Drift: A Review 概念漂移综述论文阅读

    首先这是2018年一篇关于概念漂移综述的论文[1]. 最新的研究内容包括 (1)在非结构化和噪声数据集中怎么准确的检测概念漂移.how to accurately detect concept dri ...

  3. How to Show/Hide a Button Using the Business Process Flow Stage

    How to Show/Hide a Button Using the Business Process Flow Stage In today's blog, we'll discuss how t ...

  4. 寄存器与RAM的区别

    转自:https://blog.csdn.net/qq_18191333/article/details/106912668 概述 寄存器是"存储设备",主要用于存储和检查微型计算 ...

  5. 关于一维数组传入函数的使用 //西电oj214题字符统计

    #include<stdio.h> void count(char str[],int num[]){//形参用[],传递数组首地址后可以直接正常用数组str[i] int i; for( ...

  6. listener.log/listener_scan1.log监听日志太大清理

    listener_scan1.log清理lsnrctlset current_listener listener_scan1show log_statusset log_status offcd /u ...

  7. React16下报错引发整个页面crash的解决方法

    如果报错没有没有被catch,将会引起整个React组件树的unmounting 解决方法:在生命周期中增加componentDidCatch https://reactjs.org/blog/201 ...

  8. OSPF之路由撤销

  9. vue项目 运行内存溢出

    运行vue项目报错,内存溢出!!! <--- Last few GCs ---> [10400:00000218A86135D0] 173902 ms: Mark-sweep (reduc ...

  10. 钉钉获取第三方token时提示签名时间戳参数超时的处理方法

    今天在更新平台功能时,碰到一个问题,从钉钉跳转到平台,始终不能成功.查看日志发现,出现了 签名时间戳参数超时 的错误. 想着没有动过相对应的代码,应该不是代码的问题. 查询官方文档,没有给出明确的答复 ...