Vue源码解读之InitState
admin
2024-03-25 15:33:27
0

前面我们讲到了_init函数的执行流程,简单回顾下:

  • 初始化生命周期-initLifecycle
  • 初始化事件-initEvents
  • 初始化渲染函数-initRender
  • 调用钩子函数-beforeCreate
  • 初始化依赖注入-initInjections
  • 初始化状态信息-initState
  • 初始化依赖提供-initProvide
  • 调用钩子函数-created
    一共经过上面8步,init函数执行完成,开始mount渲染。

初始化状态信息

本章咱们主要讲解initState函数的处理过程,咱们先看下init的主函数

function initState(vm: Component) {vm._watchers = []const opts = vm.$optionsif (opts.props) {initProps(vm, opts.props)}if (opts.methods) {initMethods(vm, opts.methods)}if (opts.data) {initData(vm)} else {observe(vm._data = {}, true /* asRootData */)}if (opts.computed) {initComputed(vm, opts.computed)}if (opts.watch && opts.watch !== nativeWatch) {initWatch(vm, opts.watch)}
}

看上面代码,先声明了一个_watchers的空数组;然后依次判断传递进来的options是否包含系列参数;依次执行initProps、initMethods、initData、initComputed、initWatch。

initProps

initProps函数主要是处理传进来的props对象,但是这个props对象是在上一篇文章中讲到的normalizeProps函数处理后的对象,不是传递进来的原对象。来看下initProps的代码:

function initProps(vm: Component, propsOptions: Object) {const propsData = vm.$options.propsData || {}const props = vm._props = {}const keys = vm.$options._propKeys = []const isRoot = !vm.$parentif (!isRoot) {toggleObserving(false)}for (const key in propsOptions) {keys.push(key)const value = validateProp(key, propsOptions, propsData, vm)defineReactive(props, key, value)if (!(key in vm)) {proxy(vm, `_props`, key)}}toggleObserving(true)
}

上面代码解读:

  • 第一步获取了propsData;
  • 第二步给当前实例添加了_props属性,新增了一个props引用,指向了_props属性;
  • 第三步给当前实例增加了_propKeys属性,新增了一个keys的引用,指向了_propKeys属性;
  • 第四步判断了是否需要进行监听;
  • 遍历normalizeProps函数处理后的对象propsOptions;
    • 存储key
    • 校验props格式
    • 为当前key定义响应式的属性:defineReactive
    • 把当前key的访问方式提高到实例上面:proxy,即可以vm.name来访问vm._props.name
function proxy(target: Object, sourceKey: string, key: string) {sharedPropertyDefinition.get = function proxyGetter() {return this[sourceKey][key]}sharedPropertyDefinition.set = function proxySetter(val) {this[sourceKey][key] = val}Object.defineProperty(target, key, sharedPropertyDefinition)
}
  • 打开监听。

initMethods

initMethods方法是用来处理传递进来的methods参数,把methods绑定到当前实例上面

function initMethods(vm: Component, methods: Object) {const props = vm.$options.propsfor (const key in methods) {if (process.env.NODE_ENV !== 'production') {if (typeof methods[key] !== 'function') {warn(`Method "${key}" has type "${typeof methods[key]}" in the component definition. ` + `Did you reference the function correctly?`, vm)}if (props && hasOwn(props, key)) {warn(`Method "${key}" has already been defined as a prop.`, vm)}if ((key in vm) && isReserved(key)) {warn(`Method "${key}" conflicts with an existing Vue instance method. ` + `Avoid defining component methods that start with _ or $.`)}}vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm)}
}

上面代码解读:

  • 第一步获取了props;

  • 第二步遍历methods;

    • 判断当前method是否是函数,不是函数则在开发环境下报警
    • 判断props是否已经有了当前method的key,如有则在开发环境下报警
    • 判断当前method是否已经在vm上面了,并且以$或_开头,如是,则在开发环境下报警
    • 为当前实例添加方法;

    参考 Vue面试题详细解答


initData

initData方法是用来处理传递进来的data参数,添加监听

function initData(vm: Component) {let data = vm.$options.datadata = vm._data = typeof data === 'function'? getData(data, vm): data || {}const keys = Object.keys(data)const props = vm.$options.propsconst methods = vm.$options.methodslet i = keys.lengthwhile (i--) {const key = keys[i]if (process.env.NODE_ENV !== 'production') {if (methods && hasOwn(methods, key)) {warn(`Method "${key}" has already been defined as a data property.`, vm)}}if (props && hasOwn(props, key)) {process.env.NODE_ENV !== 'production' && warn(`The data property "${key}" is already declared as a prop. ` + `Use prop default value instead.`, vm)} else if (!isReserved(key)) {// 实现代理,可以this.massage 来进行访问this._data.messageproxy(vm, `_data`, key)}}observe(data, true /* asRootData */)
}

上面代码解读:

  • 第一步获取传递进来的data,判断data是否为函数,是函数则执行函数获取当前对象,否则直接读取当前对象;
  • 第二步,获取上一步的data所有的key,赋值给keys;
  • 第三步获取props;
  • 第四步获取methods;
  • 第五步,循环keys;
    • 判断是否和methods里面是否重复,重复则开发环境进行报警
    • 判断是否和props里面是否重复,重复则开发环境进行报警
    • 判断如不是以_或$开头的key,则进行代理处理,把当前key的访问方式提高到实例上面:proxy,即可以vm.name来访问vm._datas.name
  • 对当前data对象进行observe处理,暂时先不用关注observe,后面会讲到是做什么的。

initComputed

initComputed是用来处理传进来的computed参数

function initComputed(vm: Component, computed: Object) {const watchers = vm._computedWatchers = Object.create(null)const isSSR = isServerRendering()for (const key in computed) {const userDef = computed[key]const getter = typeof userDef === 'function' ? userDef : userDef.getif (!isSSR) {watchers[key] = new Watcher(vm,getter || noop,noop,{lazy: true})}if (!(key in vm)) {defineComputed(vm, key, userDef)}}
}

initComputed方法解读:

  • 第一步为实例增加了_computedWatchers属性,声明引用watchers;
  • 获取是否是服务端渲染-isSSR;
  • 遍历computed;
    • 获取用户定义的内容-userDef
    • 根据用户定义的内容来获取当前属性key的getter函数
    • 为当前key增加Watcher,暂时不用关注Watcher后面会讲到
    • 调用defineComputed,参数为当前实例,当前属性key和userDef
      下面来看下defineComputed的实现:
function defineComputed(target: any, key: string, userDef: Object | Function) {const shouldCache = !isServerRendering()if (typeof userDef === 'function') {sharedPropertyDefinition.get = shouldCache? createComputedGetter(key): createGetterInvoker(userDef)sharedPropertyDefinition.set = noop} else {sharedPropertyDefinition.get = userDef.get? shouldCache && userDef.cache !== false? createComputedGetter(key): createGetterInvoker(userDef.get): noopsharedPropertyDefinition.set = userDef.set || noop}Object.defineProperty(target, key, sharedPropertyDefinition)
}

defineComputed

defineComputed方法解读:

  • 判断是否需要使用cache,非server端渲染,使用cache,即浏览器情况下都是true;
  • 分情况讨论:
    • userDef为函数时,调用createComputedGetter函数生成get函数,set函数为空函数
    • userDef不为函数时,get函数为createComputedGetter或者createGetterInvoker生成的函数;
  • 调用Object.defineProperty为当前实例添加定义属性;

createGetterInvoker

下面来看下createGetterInvoker:

function createGetterInvoker(fn) {return function computedGetter() {return fn.call(this, this)}
}

上面代码直接返回了一个函数,函数内部调用的是传递进来的fn函数,fn函数是从defineComputed传进来的,值为userDef或者userDef.get。

createComputedGetter

下面来看下createComputedGetter:

function createComputedGetter(key) {return function computedGetter() {const watcher = this._computedWatchers && this._computedWatchers[key]if (watcher) {if (watcher.dirty) {watcher.evaluate()}if (Dep.target) {watcher.depend()}return watcher.value}}
}

上面代码返回了一个computedGetter的函数,函数内部分析:

  • 获取了在initComputed函数里面声明的_computedWatchers,
  • watcher肯定是有值的,dirty属性的值在此处也相当于lazy属性,因为创建watcher的时候传的是true,所以此处也是true;
  • 执行watcher.evaluate,该方法会获取当前watcher的value,并且把dirty属性变为false;
  • 判断Dep.target,然后调用watcher的收集依赖;
  • 返回watcher.value;

initWatch

initWatch是用来处理传进来的watch参数。

function initWatch(vm: Component, watch: Object) {for (const key in watch) {const handler = watch[key]if (Array.isArray(handler)) {for (let i = 0; i < handler.length; i++) {createWatcher(vm, key, handler[i])}} else {createWatcher(vm, key, handler)}}
}

initWatch函数解读:
遍历watch,根据key获取handler,handler为数组遍历执行createWatcher,不为数组直接执行createWatcher;
来看下createWatcher:

createWatcher

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

createWatcher代码解读:

  • 判断handler是否为对象,如果为对象,则把当前handler作为options,options.handler作为handler;
  • 判断handler是否为字符串,字符串的话,则直接获取实例的handler方法;
  • 调用$watch返回;
    综上分析,watch的传参可以分为以下几种:
watch: {telephone: function (newValue, oldValue) {console.log('telephone')},name: 'printName',message: ['printName', 'printValue'],address: [{handler: function (newValue, oldValue) {console.log('address')}}]},methods: {printName(newValue, oldValue) {console.log('printName')},printValue(newValue, oldValue) {console.log('printValue')}}
  • 第一种:直接传方法;
  • 第二种:传递方法的字符串名称;
  • 第三种:传递方法的字符串名称数组;
  • 第四种:传递一个包含handler属性的对象数组;
    接下来咱们看下$watch方法的实现

$watch

现在咱们来看下watch的实现,watch是Vue原型上的方法,主流程篇简单提了一下,流程图上面看到$watch是在statesMixin函数里面给Vue挂载到原型对象上的。

Vue.prototype.$watch = function (expOrFn: string | Function, cb: any, options?: Object): Function {const vm: Component = thisif (isPlainObject(cb)) {return createWatcher(vm, expOrFn, cb, options)}options = options || {}options.user = trueconst watcher = new Watcher(vm, expOrFn, cb, options)if (options.immediate) {try {cb.call(vm, watcher.value)} catch (error) {handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)}}return function unwatchFn() {watcher.teardown()}
}

上面代码就是$watch函数的实现,咱们一步步来看下。

  • 参数,包含3个,第一个就是需要watch的key,比如上面例子代码的name;第二个就是回调函数,当name属性改变的时候会调用此回调函数;第三个参数为options,顾名思义,就是配置信息;
  • 第一步:实例vm的声明;
  • 第二步:判断cb是否为对象,如果是则调用上面的createWatcher;
  • 第三步:options检测是否有值,无值则赋值为空对象;
  • 第四步:设置options.user为true,即这是用户所定义和调用触发的;
  • 第五步:创建Watcher;
  • 第六步:如果是立即调用,则调用cb,即回调函数;
  • 返回一个函数,此函数为watcher的销毁函数。
    上面就是整个initWatch的调用过程。

相关内容

热门资讯

【MySQL】锁 锁 文章目录锁全局锁表级锁表锁元数据锁(MDL)意向锁AUTO-INC锁...
【内网安全】 隧道搭建穿透上线... 文章目录内网穿透-Ngrok-入门-上线1、服务端配置:2、客户端连接服务端ÿ...
GCN的几种模型复现笔记 引言 本篇笔记紧接上文,主要是上一篇看写了快2w字,再去接入代码感觉有点...
数据分页展示逻辑 import java.util.Arrays;import java.util.List;impo...
Redis为什么选择单线程?R... 目录专栏导读一、Redis版本迭代二、Redis4.0之前为什么一直采用单线程?三、R...
【已解决】ERROR: Cou... 正确指令: pip install pyyaml
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
Lock 接口解读 前置知识点Synchronized synchronized 是 Java 中的关键字,...
Win7 专业版安装中文包、汉... 参考资料:http://www.metsky.com/archives/350.htm...
3 ROS1通讯编程提高(1) 3 ROS1通讯编程提高3.1 使用VS Code编译ROS13.1.1 VS Code的安装和配置...
大模型未来趋势 大模型是人工智能领域的重要发展趋势之一,未来有着广阔的应用前景和发展空间。以下是大模型未来的趋势和展...
python实战应用讲解-【n... 目录 如何在Python中计算残余的平方和 方法1:使用其Base公式 方法2:使用statsmod...
学习u-boot 需要了解的m... 一、常用函数 1. origin 函数 origin 函数的返回值就是变量来源。使用格式如下...
常用python爬虫库介绍与简... 通用 urllib -网络库(stdlib)。 requests -网络库。 grab – 网络库&...
药品批准文号查询|药融云-中国... 药品批文是国家食品药品监督管理局(NMPA)对药品的审评和批准的证明文件...
【2023-03-22】SRS... 【2023-03-22】SRS推流搭配FFmpeg实现目标检测 说明: 外侧测试使用SRS播放器测...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
初级算法-哈希表 主要记录算法和数据结构学习笔记,新的一年更上一层楼! 初级算法-哈希表...
进程间通信【Linux】 1. 进程间通信 1.1 什么是进程间通信 在 Linux 系统中,进程间通信...
【Docker】P3 Dock... Docker数据卷、宿主机与挂载数据卷的概念及作用挂载宿主机配置数据卷挂载操作示例一个容器挂载多个目...