vue源码:Watcher系列(一)

您所在的位置:网站首页 久宇舞弥的儿子西格玛 vue源码:Watcher系列(一)

vue源码:Watcher系列(一)

2023-10-05 08:37| 来源: 网络整理| 查看: 265

少年驰骋,仗剑天涯 愿你眼眸有星辰,心中有大海 从此,以梦为马,不负韶华

在分析之前我们先来看看,vue中都有哪些Watcher种类呢?以及分别在什么时候创建呢?从vue源码里面看,Watcher是一个公共类,在不同的地方去初始化Watcher就代表不同类的Watcher。主要分为以下三类:

computed watcher:执行计算属性更新computeduser watcher:用户注册的普通watcherrender watcher:负责视图更新

注意者三种watcher的执行顺序为computed watcher、user watcher、render watcher

这么执行原因也很简单,当computed、watcher将数据更新完成之后,要渲染到我们的视图上的时候触发render watcher。

好了,下面我们来看看vue中三种watcher的创建时机。

Render Watcher

首先我们来看Render Watcher.在模板开始渲染的时候,也就是mount初始化的过程中创建的。

每一个组件都有一个render watcher,当data或者computed中有被依赖的属性改变时就会触发render-watcher进行更新。更新代码updateComponent = () => {vm._update(vm._render(), hydrating)}

源码位置:src/core/instance/lifecycle.js

export function mountComponent ( vm: Component, el: ?Element, hydrating?: boolean ): Component { // 获取el,也就是dom vm.$el = el console.log("lifecycle.js",el) // 判断实例上是否存在渲染函数,如果不存在; // 则设置一个默认的渲染函数createEmptyVNode,render相当于createEmptyVNode //createEmptyVNode :创建一个节点为空的vNode函数 if (!vm.$options.render) { vm.$options.render = createEmptyVNode if (process.env.NODE_ENV !== 'production') { /* istanbul ignore if */ if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') || vm.$options.el || el) { warn( 'You are using the runtime-only build of Vue where the template ' + 'compiler is not available. Either pre-compile the templates into ' + 'render functions, or use the compiler-included build.', vm ) } else { warn( 'Failed to mount component: template or render function not defined.', vm ) } } } //调用callHook函数来触发beforeMount生命周期钩子函数,可以为理解此时只是render 函数并没有形成虚拟dom, // 也没有将页面内容真正渲染上 callHook(vm, 'beforeMount') // 定义updateComponent,这个函数中参数'vm._render()'将会为我们得到一份最新的VNode // 节点树,'如果调用了updateComponent函数,就会将最新的模板内容渲染到视图页面中' let updateComponent /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { updateComponent = () => { const name = vm._name const id = vm._uid const startTag = `vue-perf-start:${id}` const endTag = `vue-perf-end:${id}` mark(startTag) const vnode = vm._render() mark(endTag) measure(`vue ${name} render`, startTag, endTag) mark(startTag) vm._update(vnode, hydrating) mark(endTag) measure(`vue ${name} patch`, startTag, endTag) } } else { updateComponent = () => { vm._update(vm._render(), hydrating) } console.log(vm._update) } // updateComponent函数作为第二个参数传给Watcher类从而创建了watcher实例,那么 // updateComponent函数中读取的所有数据都将被watcher所监控 ------------------------------------------------ new Watcher(vm, updateComponent, noop, { before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) hydrating = false ------------------------------------------------ if (vm.$vnode == null) {//vm.$vnode为null代表着不是一次组件的初始化过程,而是外部通过new vue初始化过程 vm._isMounted = true //标志位表示挂载已完成,调用mounted钩子函数 callHook(vm, 'mounted') } return vm } Computed Watcher

在vue的初始化阶段,调用initState,判断对象的computed师傅存在值,如果存在则调用initComputed初始化computed,在这个方法中对computed Watcher进行了初始化。

源码位置:src/core/instance/state.js

export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options ... if (opts.computed) initComputed(vm, opts.computed) ... }

在initComputed中,首先定义了一个空的watchers对象对象迎来存储所有的计算属性对应的watcher;接着遍历组件中定义的计算属性,且对其添加watcher进行监听(调用defineComputed添加get和set)。

function initComputed (vm: Component, computed: Object) { // 定义一个watchers空对象,用于存储所有计算属性对应的watcher const watchers = vm._computedWatchers = Object.create(null) // computed properties are just getters during SSR const isSSR = isServerRendering() //遍历每个计算属性 for (const key in computed) { const userDef = computed[key] //userDef的每一项都是computed对应的回调函数 const getter = typeof userDef === 'function' ? userDef : userDef.get if (process.env.NODE_ENV !== 'production' && getter == null) { warn( `Getter is missing for computed property "${key}".`, vm ) } if (!isSSR) { // 创建一个内部的watcher给计算属性 watchers[key] = new Watcher( vm,//组件实例 getter || noop, noop, computedWatcherOptions//配置 ) } // 判断如果key不是vm的属性,则执行defineComputed()函数 if (!(key in vm)) { defineComputed(vm, key, userDef) } else if (process.env.NODE_ENV !== 'production') { //接着判断当前计算属性是否与data或者props中定义的变量冲突 if (key in vm.$data) { warn(`The computed property "${key}" is already defined in data.`, vm) } else if (vm.$options.props && key in vm.$options.props) { warn(`The computed property "${key}" is already defined as a prop.`, vm) } else if (vm.$options.methods && key in vm.$options.methods) { warn(`The computed property "${key}" is already defined as a method.`, vm) } } } }

这里defineComputed内部其实就是使用Object.defineproperty让computed数据成为一个响应式数据,并为他定义get属性,即页面渲染的时候访问computed时会触发他的get。然后执行CreateComputedGetter方法:

function createComputedGetter (key) { return function computedGetter () { const watcher = this._computedWatchers && this._computedWatchers[key] if (watcher) { if (watcher.dirty) {//true表示需要计算 watcher.evaluate() } if (Dep.target) { //这是页面渲染的时候出发,因此是render-watcher watcher.depend() //收集当前的watcher } return watcher.value } } } User Watcher

这个也是在初始化阶段创建的。不同的是user watcher是在initWatch中创建的,且它是一个个的去遍历创建。

在src/core/instance/state.js文件中initState中调用initWatch()初始化User watcher.

export function initState (vm: Component) { vm._watchers = [] const opts = vm.$options ... if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch) } }

在initWatch中,会逐个遍历watch列表,逐个创建Watcher实例。

function initWatch (vm: Component, watch: Object) { //遍历watch对象列表,逐个创建Watcher实例 for (const key in watch) { //handler可以是数组或者对象 const handler = watch[key] if (Array.isArray(handler)) { for (let i = 0; i createWatcher(vm, key, handler) } }

上面代码仅仅是将watch列表进行了遍历,真正去创建是使用createWatcher。而下面的方法内部又是使用$watch给每个watch分发watcher.

function createWatcher ( vm: Component, keyOrFn: string | Function, handler: any, options?: Object ) { if (isPlainObject(handler)) { options = handler handler = handler.handler } if (typeof handler === 'string') { handler = vm[handler] } return vm.$watch(keyOrFn, handler, options) }

以上就是Vue中三种watcher的创建,vue对于不同的watcher他的创建都有所不同,但是我看到很多文章都是混在一起说的,导致读者会产生歧义。

本文呢是我阅读完源码后,基于个人角度输出的文章,可能有些地方理解的还是不够,后面还会继续去更新这个系列。希望对大家有用!



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3