详解Vue中的watch和computed

更新时间:2023-05-23 18:27:22 阅读: 评论:0

自信的表现-文字综合

详解Vue中的watch和computed
2023年5月23日发(作者:华龙证卷)

详解Vue中的watchcomputed

前⾔

对于使⽤Vue的前端⽽⾔,watchcomputedmethods三个属性相信是不陌⽣的,是⽇常开发中经常使⽤的属性。但是对于

它们的区别及使⽤场景,⼜是否清楚,本⽂我将跟⼤家⼀起通过源码来分析这三者的背后实现原理,更进⼀步地理解它们所代

表的含义。 在继续阅读本⽂之前,希望你已经具备了⼀定的Vue使⽤经验,如果想学习Vue相关知识,请移步⾄官⽹。

Watch

我们先来找到watch的初始化的代码,/src/core/instance/

export function initState (vm: Component) {

vm._watchers = []

const opts = vm.$options

if () initProps(vm, ) // 初始化props

if (s) initMethods(vm, s) // 初始化⽅法

if () {

initData(vm) // 先初始化data 重点

} el {

obrve(vm._data = {}, true /* asRootData */)

}

if (ed) initComputed(vm, ed) // 初始化computed

if ( && !== nativeWatch) {

initWatch(vm, ) // 初始化watch

}

}

接下来我们深⼊分析⼀下initWatch的作⽤,不过在接下去之前,这⾥有⼀点是data的初始化是在computedwatch初始化之

前,这是为什么呢?⼤家可以停在这⾥想⼀下这个问题。想不通也没关系,继续接下来的源码分析,这个问题也会迎刃⽽解。

initWatch

function initWatch (vm: Component, watch: Object) {

for (const key in watch) {

const handler = watch[key]

if (y(handler)) { // 如果handler是⼀个数组

for (let i = 0; i < ; i++) { // 遍历watch的每⼀项,执⾏createWatcher

createWatcher(vm, key, handler[i])

}

} el {

createWatcher(vm, key, handler)

}

}

}

createWatcher

function createWatcher (

vm: Component,

expOrFn: string | Function,

handler: any,

options?: Object

) {

if (isPlainObject(handler)) { // 判断handler是否是纯对象,optionshandler重新赋值

options = handler

handler = r

}

if (typeof handler === 'string') { // handler⽤的是methods上⾯的⽅法,具体⽤法请查看官⽹⽂档

handler = vm[handler]

}

// expOrnFn: watchkey值, handler: 回调函数 options: 可选配置

return vm.$watch(expOrFn, handler, options) // 调⽤原型上的$watch

}

ype.$watch

ype.$watch = function (

expOrFn: string | Function,

cb: any,

options?: Object

): Function {

const vm: Component = this

if (isPlainObject(cb)) { // 判断cb是否是对象,如果是则继续调⽤createWatcher

return createWatcher(vm, expOrFn, cb, options)

}

options = options || {}

= true // ur Watcher的标⽰ options = { ur: true ...options }

const watcher = new Watcher(vm, expOrFn, cb, options) // new Watcher ⽣成⼀个ur Watcher

if (ate) { // 如果传⼊了immediate 则直接执⾏回调cb

try {

(vm, )

} catch (error) {

handleError(error, vm, `callback for immediate watcher "${sion}"`)

}

}

return function unwatchFn () {

wn()

}

}

}

上⾯⼏个函数调⽤的逻辑都⽐较简单,所以就在代码上写了注释。我们重点关注⼀下这个urWatcher⽣成的时候做了什么。

Watcher

⼜来到了我们⽐较常见的Watcher类的阶段了,这次我们重点关注⽣成urWatch的过程。

export default class Watcher {

vm: Component;

expression: string;

cb: Function;

id: number;

deep: boolean;

ur: boolean;

lazy: boolean;

sync: boolean;

dirty: boolean;

active: boolean;

deps: Array;

newDeps: Array;

depIds: SimpleSet;

newDepIds: SimpleSet;

before: ?Function;

getter: Function;

value: any;

constructor (

vm: Component,

expOrFn: string | Function,

cb: Function,

options?: ?Object,

isRenderWatcher?: boolean

) {

= vm

if (isRenderWatcher) {

vm._watcher = this

}

vm._(this)

// options

if (options) { // new UrWatcher的时候传⼊了options,并且 = true

= !!

= !!

= !!

= !!

=

} el {

= = = = fal

}

= cb

= ++uid // uid for batching

= true

= // for lazy watchers

= []

s = []

= new Set()

Ids = new Set()

sion = _ENV !== 'production' // ⼀个函数表达式

ng()

: ''

// par expression for getter

if (typeof expOrFn === 'function') {

= expOrFn

} el {

= parPath(expOrFn) // 进⼊这个逻辑,调⽤parPath⽅法,对getter进⾏赋值

if (!) {

= noop

_ENV !== 'production' && warn(

`Failed watching path: "${expOrFn}" ` +

'Watcher only accepts simple dot-delimited paths. ' +

'For full control, u a function instead.',

vm

)

}

}

=

undefined

: ()

}

}

⾸先会对这个watcher的属性进⾏⼀系列的初始化配置,接着判断expOrFn这个值,对于我们watchkey⽽⾔,不是函数所以

会执⾏parPath函数,该函数定义如下:

/**

* Par simple path.

*/

const bailRE = new RegExp(`[^${}.$_d]`)

export function parPath (path: string): any {

if ((path)) {

return

}

const gments = ('.')

return function (obj) {

for (let i = 0; i < ; i++) { // 遍历数组

if (!obj) return

obj = obj[gments[i]] // 每次把当前的key值对应的值重新赋值obj

}

return obj

}

}

⾸先会判断传⼊的path是否符合预期,如果不符合则直接return,接着讲path根据'.'字符串进⾏拆分,因为我们传⼊的watch可能

有如下⼏种形式:

watch: {

a: () {}

'formData.a': () {}

}

所以需要对path进⾏拆分,接下来遍历拆分后的数组,这⾥返回的函数的参数obj其实就是vm实例,通过vm[gments[i]],就可

以最终找到这个watch所对应的属性,最后将obj返回。

constructor () { // 初始化的最后⼀段逻辑

= // 因为fal,所以会执⾏⽅法

undefined

: ()

}

get () {

pushTarget(this) // 将当前的watcher实例赋值给

let value

const vm =

try {

value = (vm, vm) // 这⾥的getter就是上⽂所讲parPath放回的函数,并将vm实例当做第⼀个参数传⼊

} catch (e) {

if () {

handleError(e, vm, `getter for watcher "${sion}"`) // 如果报错了会这这⼀块逻辑

} el {

throw e

}

} finally {

// "touch" every property so they are all tracked as

// dependencies for deep watching

if () { // 如果deeptrue,则执⾏深递归

traver(value)

}

popTarget() // 将当前watch出栈

pDeps() // 清空依赖收集 这个过程也是尤为重要的,后续我会单独写⼀篇⽂章分析。

}

return value

}

对于UrWatcher的初始化过程,我们基本上就分析完了,traver函数本质就是⼀个递归函数,逻辑并不复杂,⼤家可以⾃

⾏查看。 初始化过程已经分析完,但现在我们好像并不知道watch到底是如何监听data的数据变化的。其实对于UrWatcher

的依赖收集,就发⽣在⽅法中,通过(parPath)函数,我们就访问了vm实例上的属性。因为这个时候已

initData,所以会触发对应属性的getter函数,这也是为什么initData会放在initWatchinitComputed函数前⾯。所以当前的

UrWatcher就会被存放进对应属性Dep实例下的subs数组中,如下:

Property(obj, key, {

enumerable: true,

configurable: true,

get: function reactiveGetter () {

const value = getter ? (obj) : val

if () {

()

if (childOb) {

()

if (y(value)) {

dependArray(value)

}

}

}

return value

},

}

前⼏个篇章我们都提到renderWatcher,就是视图的初始化渲染及更新所⽤。这个renderWathcer初始化的时机是在我们执⾏

$mount⽅法的时候,这个时候⼜会对data上的数据进⾏了⼀遍依赖收集,每⼀个datakeyDep实例都会将renderWathcer

放到⾃⼰的subs数组中。如图:

, 当我们对data上的数据进⾏修改时,就会触发对应属性的tter函数,进⽽触发(),遍历subs中的每⼀个watcher

执⾏()函数->,renderWathcerupdate⽅法我们就不深究了,不清楚的同学可以参考下我写的。

于我们分析的UrWatcher⽽⾔,相关代码如下:

class Watcher {

constructor () {} //..

run () {

if () { // ⽤于标⽰watcher实例有没有注销

const value = () // 执⾏get⽅法

if ( // ⽐较新旧值是否相同

value !== ||

// Deep watchers and watchers on Object/Arrays should fire even

// when the value is the same, becau the value may

// have mutated.

isObject(value) ||

) {

// t new value

const oldValue =

= value

if () { // UrWatcher

try {

(, value, oldValue) // 执⾏回调cb,并传⼊新值和旧值作为参数

} catch (e) {

handleError(e, , `callback for watcher "${sion}"`)

}

} el {

(, value, oldValue)

}

}

}

}

}

⾸先会判断这个watcher是否已经注销,如果没有则执⾏⽅法,重新获取⼀次新值,接着⽐较新值和旧值,如果相同则

不继续执⾏,若不同则执⾏在初始化时传⼊的cb回调函数,这⾥其实就是handler函数。⾄此,UrWatcher的⼯作原理就分

析完了。接下来我们来继续分析ComputedWatcher,同样的我们找到初始代码

Computed

initComputed

const computedWatcherOptions = { lazy: true }

function initComputed (vm: Component, computed: Object) {

// $flow-disable-line

const watchers = vm._computedWatchers = (null) // ⽤来存放computedWatchermap

// computed properties are just getters during SSR

const isSSR = isServerRendering()

for (const key in computed) {

const urDef = computed[key]

const getter = typeof urDef === 'function' ? urDef :

if (_ENV !== 'production' && getter == null) {

warn(

`Getter is missing for computed property "${key}".`,

vm

)

}

if (!isSSR) { // 不是服务端渲染

// create internal watcher for the computed property.

watchers[key] = new Watcher( // 执⾏new Watcher

vm,

getter || noop,

noop,

computedWatcherOptions { lazy: true }

)

}

// component-defined computed properties are already defined on the

// component prototype. We only need to define computed properties defined

// at instantiation here.

if (!(key in vm)) {

// 会在vm的原型上去查找computed对应的key值存不存在,如果不存在则执⾏defineComputed,存在的话则退出,

// 这个地⽅其实是Vue精⼼设计的

// ⽐如说⼀个组件在好⼏个⽂件中都引⽤了,如果不将computed

defineComputed(vm, key, urDef)

} el if (_ENV !== 'production') {

if (key in vm.$data) {

warn(`The computed property "${key}" is already defined in data.`, vm)

} el if (vm.$ && key in vm.$) {

warn(`The computed property "${key}" is already defined as a prop.`, vm)

}

}

}

}

defineComputed

new Watcher的逻辑我们先放⼀边,我们先关注⼀下defineComputed这个函数到底做了什么

export function defineComputed (

target: any,

key: string,

urDef: Object | Function

) {

const shouldCache = !isServerRendering()

if (typeof urDef === 'function') { // 分⽀1

= shouldCache

createComputedGetter(key)

: createGetterInvoker(urDef)

= noop

} el {

=

shouldCache && !== fal

createComputedGetter(key)

: createGetterInvoker()

: noop

= || noop

}

if (_ENV !== 'production' &&

=== noop) {

= function () {

warn(

`Computed property "${key}" was assigned to but it has no tter.`,

this

)

}

}

Property(target, key, sharedPropertyDefinition)

}

这个函数本质也是调⽤Property来改写computedkey值对应的getter函数和tter函数,当访问到key的时候,

就会触发其对应的getter函数,对于⼤部分情况下,我们会⾛到分⽀1,对于不是服务端渲染⽽

⾔,会被createComputedGetter(key)赋值,t会被赋值为⼀个空函数。

createComputedGetter

function createComputedGetter (key) {

return function computedGetter () {

const watcher = this._computedWatchers && this._computedWatchers[key] // 就是上⽂中new Watcher()

if (watcher) {

if () {

te()

}

if () {

()

}

return

}

}

}

可以看到createComputedGetter(key)其实会返回⼀个computedGetter函数,也就是说在执⾏render函数时,访问到这个

vm[key]对应的computed的时候会触发getter函数,⽽这个getter函数就是computedGetter

export default {

data () {

return {

a: 1,

b: 2

}

},

computed: {

message () { // 这⾥的函数名message就是所谓的key

return this.a + this.b

}

}

}

以上代码为例⼦,来⼀步步解析computedGetter函数。 ⾸先我们需要先获取到key对应的watcher.

const watcher = this._computedWatchers && this._computedWatchers[key]

⽽这⾥的watcher就是在initComputed函数中所⽣成的。

if (!isSSR) { // 不是服务端渲染

// create internal watcher for the computed property.

watchers[key] = new Watcher( // 执⾏new Watcher

vm,

getter || noop,

noop,

computedWatcherOptions { lazy: true }

)

}

我们来看看computedWatcher的初始化过程,我们还是接着来继续回顾⼀下Watcher类相关代码

export default class Watcher {

vm: Component;

expression: string;

cb: Function;

id: number;

deep: boolean;

ur: boolean;

lazy: boolean;

sync: boolean;

dirty: boolean;

active: boolean;

deps: Array;

newDeps: Array;

depIds: SimpleSet;

newDepIds: SimpleSet;

before: ?Function;

getter: Function;

value: any;

constructor (

vm: Component,

expOrFn: string | Function,

cb: Function,

options?: ?Object,

isRenderWatcher?: boolean

) {

= vm

if (isRenderWatcher) {

vm._watcher = this

}

vm._(this)

// options

if (options) {

= !!

= !!

= !! // lazy = true

= !!

=

} el {

= = = = fal

}

= cb

= ++uid // uid for batching

= true

= // for lazy watchers = true 这⾥把设置为true

= []

s = []

= new Set()

Ids = new Set()

sion = _ENV !== 'production'

ng()

: ''

// par expression for getter

if (typeof expOrFn === 'function') { // ⾛到这⼀步

= expOrFn

} el {

// ..

}

= // ⼀开始不执⾏()函数 直接返回undefined

undefined

: ()

}

紧接着回到computedGetter函数中,执⾏剩下的逻辑

if (watcher) {

if () {

te()

}

if () {

()

}

return

}

⾸先判断watcher是否存在,如果存在则执⾏以下操作

判断是否为true,如果为true,则执⾏te

判断当前是否存在,存在则执⾏

最后返回

computedWatcher初始化的时候,由于传⼊的true,所以相应的也为true,当我们在执⾏render

数的时候,访问到message,触发了computedGetter,所以会执⾏te

evaluate () {

= () // 这⾥的get() 就是vm['message'] 返回就是this.a + this.b的和

= fal // dirty置为fal

}

同时这个时候由于访问vm上的a属性和b属性,所以会触发abgetter函数,这样就会把当前这个computedWatcher加⼊到

ab对应的Dpe实例下的subs数组中了。如图:

接着当前的毫⽆疑问就是renderWatcher了,并且也是存在的,所以就执⾏了()

depend () {

let i =

while (i--) {

[i].depend()

}

}

对于当前的message computedWatcher⽽⾔,其实就是ab两个属性对应的Dep实例,接着遍历整个deps,对每⼀

dep就进⾏depend()操作,也就是每⼀个Dep实例把当前的(renderWatcher都加⼊到各⾃的subs中,如图:

所以这个时候,⼀旦你修改了ab的其中⼀个值,都会触发tter函数->()->,代码如下:

update () {

/* istanbul ignore el */

if () {

= true

} el if () {

()

} el {

queueWatcher(this)

}

}

总结

其实不管是watch还是computed本质上都是通过watcher来实现,只不过它们的依赖收集的时机会有所不同。就使⽤场景⽽

⾔,computed多⽤于⼀个值依赖于其他响应式数据,⽽watch主要⽤于监听响应式数据,在进⾏所需的逻辑操作!⼤家可以通

过单步调试的⽅法,⼀步步调试,能更好地加深理解。

以上就是详解Vue中的watchcomputed的详细内容,更多关于Vue watchcomputed的资料请关注其它相关⽂章!

幼儿园消防安全-创文工作

详解Vue中的watch和computed

本文发布于:2023-05-23 18:27:22,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/zhishi/a/168483764251173.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:详解Vue中的watch和computed.doc

本文 PDF 下载地址:详解Vue中的watch和computed.pdf

标签:watcher
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 实用文体写作网旗下知识大全大全栏目是一个全百科类宝库! 优秀范文|法律文书|专利查询|