Vue侦测相关api的实现方法
作者:Infinity 发布时间:2024-05-09 09:52:46
vm.$watch
用法: vm.$watch( expOrFn, callback, [options] ) ,返回值为 unwatch 是一个函数用来取消观察;下面主要理解 options 中的两个参数 deep 和 immediate 以及 unwatch
Vue.prototype.$watch = function (expOrFn, cb, options) {
const vm = this
options = options || {}
const watcher = new Watcher(vm, expOrFn, cb, options)
if(options.immediate) {
cb.call(vm, watcher,.value)
}
return function unwatchFn() {
watcher.teardown()
}
}
immediate
从上面代码中可以看出当 immediate 为 true 时,就会直接进行执行回调函数
unwatch
实现方式是:
将被访问到的数据 dep 收集到 watchs 实例对象上,通过 this.deps 存起来
将被访问到的数据 dep.id 收集到 watchs 实例对象上,通过 this.depIds 存起来
最后通过 watchs 实例对象的 teardown 进行删除
class Watcher {
constructor (vm, expOrFn, cb) {
this.vm = vm
this.deps = []
this.depIds = new Set()
if(typeof expOrFn === 'function') {
this.getter = expOrFn
}else {
this.getter = parsePath(expOrFn)
}
this.cb = cb
this.value = this.get()
}
....
addDep (dep) {
const id = dep.id //参数dep是Dep实例对象
if(!this.depIds.has(id)) { //判断是否存在避免重复添加
this.depIds.add(id)
this.deps.push(dep)
dep.addSub(this) //this 是依赖
}
}
teardown () {
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
}
}
let uid = 0
class Dep {
constructor () {
this.id = uid++
...
}
...
depend () {
if(window.target) {
window.target.addDep(this) //将this即当前dep对象加入到watcher对象上
}
}
removeSub (sub) {
const index = this.subs.indexOf(sub)
if(index > -1) {
return this.subs.splice(index, 1)
}
}
}
分析
当执行 teardown() 时需要循环;因为例如 expOrFn = function () { return this.name + this.age } ,这时会有两个 dep 分别是 name 与 age 分别都加入了 watcher 依赖( this ),都会加入到 this.deps 中,所以需要循环将含有依赖的 dep 都删除其依赖
deep
需要明白的是
deep 干啥用的,例如 data = {arr: [1, 2, {b: 6]} ,当我们只是监听 data.arr 时,在 [1, 2, {b: 66}] 这个数值内部发生变化时,也需要触发,即 b = 888
怎么做呢?
class Watcher {
constructor (vm, expOrFn, cb, options) {
this.vm = vm
this.deps = []
this.depIds = new Set()
if(typeof expOrFn === 'function') {
this.getter = expOrFn
}else {
this.getter = parsePath(expOrFn)
}
if(options) { //取值
this.deep = !!options.deep
}else {
this.deep = false
}
this.cb = cb
this.value = this.get()
}
get () {
window.target = this
let value = this.getter.call(vm, vm)
if(this.deep) {
traverse(value)
}
window.target = undefined
return value
}
...
}
const seenObjects = new Set()
function traverse (val) {
_traverse(val, seenObjects)
seenObjects.clear()
}
function _traverse(val, seen) {
let i, keys
const isA = Array.isArray(val)
if((!isA && isObject(val)) || Object.isFrozen(val)) { //判断val是否是对象或者数组以及是否被冻结
return
}
if(val._ob_) {
const depId = val._ob_.dep.id //可以看前面一篇我们对Observer类添加了this.dep = new Dep(),所以能访问其dep.id
if(seen.has(depId)) {
return
}
seen.add(depId)
}
if(isA) {
i = val.length
while (i--) _traverse(val[i], seen)
} else {
keys = Object.keys(val)
i = keys.length
while (i--) _traverse(val[i], seen)
}
}
分析
window.target = this ,寄存依赖
let value = this.getter.call(vm, vm) 访问当前val,并执行 get
的 dep.depend() ,如果发现 val 为数组,则将依赖加入到 observer 的 dep 中,也就实现了对当前数组的拦截
traverse(value) 也就是执行 _traverse(val, seenObjects) ;核心就是对被 Observer 的 val 通过 val[i] 通过这种操作,间接触发 get ,将依赖添加到当前数值的 dep 中,这样也就实现了,当内部数据发生变化,也会循环 subs 执行依赖的 update ,从而触发回调;当是数组时,只需进行遍历,看内部是否有 Object 对象即可,因为在第二步的时候,会对 val 进行判断是否是数组,变改变七个方法的value,在遍历;所以这边只要是内部数组都会进行拦截操作,添加依赖,即对象 {} 这种没没添加依赖。
seenObjects.clear() 当内部所以类型数据都添加好其依赖后,就清空。
window.target = undefined 消除依赖
vm.$set
用法: vm.$set(target, key, value)
作用
对于数组,进行 set 则是添加新元素,并需要触发依赖更新
对于对象,如果 key 值存在,则是修改 value ;不存在,则是添加新元素,需新元素要进行响应式处理,以及触发更新
对于对象本身不是响应式,则直接添加 key-value ,无需处理
Vue.prototype.$set = function (target, key, val) {
if(Array.isArray(target) && isValidArrayIndex(key)) { //是数组并且key有效
target.length = Math.max(target.length, key) //处理key > target.length
target.splice(key, 1, val) //添加新元素,并输出依赖更新同时新元素也会进行`Obsever`处理
return val
}
if(key in targert && !(key in Object.prototype) { //能遍历并且是自身key
target[key] = val //触发set,执行依赖更新
return val
}
const ob = target._ob_
if(target.isVue || (ob && ob.vm.Count) { //不是vue实例也不是vue实例的根对象(即不是this.$data跟对象)
//触发警告
return
}
if(!ob) { //只添加
target[key] = val
return val
}
defineReactive(ob.value, key, val) //进行响应式处理
ob.dep.notify() //触发依赖更新
returnv val
}
vm.$delete
用法: vm.$delete( target, key)
作用
对于数组,进行 delete 则是删除新元素,并需要触发依赖更新
对于对象,如果 key 值不存在,直接 return ,存在,删除元素,
对于对象本身不是响应式,则只删除 key-value ,无需其他处理
Vue.prototype.$delete = function (target, key) {
if(Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
const ob = target._ob_
if(target.isVue || (ob && ob.vm.Count) { //不是vue实例也不是vue实例的根对象(即不是this.$data跟对象)
//触发警告
return
}
if(!hasOwn(target, key)) {
return
}
delete target[key]
if(!ob) {
return
}
ob.dep.notify()
}
来源:https://segmentfault.com/a/1190000019253948


猜你喜欢
- 1、字典中的键存在时,可以通过字典名+下标的方式访问字典中改键对应的值,若键不存在则会抛出异常。如果想直接向字典中添加元素可以直接用字典名+
- 本文记录了RHEL7.5下mysql 8.0.11安装教程,具体内容如下首先去mysql官网下载mysql-8.0.11-el7-x86_6
- 0. 前言本节中,我们使用策略梯度算法解决 CartPole 问题。虽然在这个简单问题中,使用随机搜索策略和爬山算法就足
- 1.SGD随机梯度下降随机梯度下降和其他的梯度下降主要区别,在于SGD每次只使用一个数据样本,去计算损失函数,求梯度,更新参数。这种方法的计
- 前言第一次看go基础语法的时候,用使用到了defer。但是一直不知道它到底是什么,有什么用途。这几天通过查询、学习。算是对defer有了一点
- 1.创建虚拟环境首先创建一个新文件夹在PyCharm终端中切换到这个文件夹,输入 python -m venv 环境名 创建虚拟环境&nbs
- 今天在写vue项目时,用到了computed计算属性,遇到了使用箭头函数出现this指向问题,这里记录下1.箭头函数中的this箭头函数内部
- Step1:确定操作系统Python 解释器的下载地址为:https://www.python.org/ ,点击&nbs
- 导言本文简单介绍了如何从网易财经获取某支股票的价格数据,并根据价格数据画出相应的日K线图。有助于新手了解并使用Python的相关功能。包括列
- 该平台会集成UI自动化及api自动化,里面也会涉及到一些简单的HTML等前端,当然都是很基础的东西。在以后的博客里,我会一点点的尽量写详细,
- 第一次用layui,正在摸索中,今天在学习layui的时候在项目中看到一个表单提交,表单的数据传到后台是怎么自动封装到实体类里面的呢?1、表
- 将np图片(imread后的图片)转码为base64格式def image_to_base64(image_np):image = cv2.
- QCalendarWidget 是日历控件。它允许用户以简单和直观的方式选择日期。#!/usr/bin/python3# -*- codin
- on和where的区别多表查询语法结构:table_reference {[INNER] JOIN | {LEFT|RIGHT} [OUTE
- 如何做一个只搜索本网站的引擎? 用下面两个文件即可实现:searchfiles.html &l
- 字符串转日期、日期转字符串// 2014-02-25 /** * 字符串转时间(yyyy-MM-dd HH:mm:ss)
- 拼接字符串使用“+”可以对多个字符串进行拼接语法格式: str1 + str2>>> str1 = "aaa&q
- python使用ctypes调用C/C++1. ctpes介绍ctypes is a foreign function library fo
- #pd.to_datetime函数#读取数据import pandas as pddata = pd.read_csv('polic
- 一、在 VS Code 中配置调试使用 Vue CLI 2搭建项目时:更新 config/index.js 内的 devtool prope