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的调用过程。

相关内容

热门资讯

安卓系统的如何测试软件,从入门... 你有没有想过,你的安卓手机里那些神奇的软件是怎么诞生的呢?它们可不是凭空出现的,而是经过一系列严格的...
小米8安卓系统版本,安卓系统版... 你有没有发现,手机更新换代的速度简直就像坐上了火箭呢?这不,小米8这款手机自从上市以来,就凭借着出色...
华为手机安卓系统7以上,创新体... 你有没有发现,最近华为手机越来越受欢迎了呢?尤其是那些搭载了安卓系统7.0及以上版本的机型,简直让人...
儿童英语免费安卓系统,儿童英语... 哇,亲爱的家长朋友们,你是否在为孩子的英语学习发愁呢?别担心,今天我要给你带来一个超级好消息——儿童...
ios系统切换安卓系统还原,还... 你有没有想过,有一天你的手机从iOS系统切换到了安卓系统,然后再从安卓系统回到iOS系统呢?这听起来...
灵焕3装安卓系统,引领智能新体... 你知道吗?最近手机圈里可是掀起了一股热潮,那就是灵焕3这款神器的安卓系统升级。没错,就是那个曾经以独...
安卓系统指南针软件,探索未知世... 手机里的指南针功能是不是让你在户外探险时倍感神奇?但你知道吗,安卓系统中的指南针软件可是大有学问呢!...
华为是不用安卓系统了吗,迈向自... 最近有个大新闻在科技圈里炸开了锅,那就是华为是不是不再使用安卓系统了?这可不是一个简单的问题,它涉及...
安卓系统热点开启失败,排查与解... 最近是不是你也遇到了安卓系统热点开启失败的小麻烦?别急,让我来给你详细说说这个让人头疼的问题,说不定...
小米max2系统安卓,安卓系统... 你有没有听说过小米Max2这款手机?它那超大的屏幕,简直就像是个移动的电脑屏幕,看视频、玩游戏,那叫...
电池健康怎么保持安卓系统,优化... 手机可是我们生活中不可或缺的好伙伴,而电池健康度就是它的生命力。你有没有发现,随着使用时间的增长,你...
安卓手机怎么调系统颜色,安卓手... 你有没有发现,你的安卓手机屏幕颜色突然变得不那么顺眼了?是不是也想给它换换“脸色”,让它看起来更有个...
安卓系统清粉哪个好,哪款清粉工... 手机用久了,是不是觉得卡得要命?别急,今天就来聊聊安卓系统清理垃圾哪个软件好。市面上清理工具那么多,...
华为被限制用安卓系统,挑战安卓... 你知道吗?最近科技圈可是炸开了锅!华为,这个我们耳熟能详的名字,竟然因为一些“小插曲”被限制了使用安...
安卓系统是不是外国,源自外国的... 你有没有想过,我们每天离不开的安卓系统,它是不是外国货呢?这个问题听起来可能有点奇怪,但确实很多人都...
安卓系统缺少文件下载,全面解析... 你有没有发现,用安卓手机的时候,有时候下载个文件真是让人头疼呢?别急,今天就来聊聊这个让人烦恼的小问...
kktv系统刷安卓系统怎么样,... 你有没有听说最近KKTV系统刷安卓系统的事情?这可是个热门话题呢!咱们一起来聊聊,看看这个新玩意儿到...
安卓系统连接电脑蓝牙,操作指南... 你有没有遇到过这种情况:手机里堆满了各种好用的应用,可就是想找个方便快捷的方式,把手机里的音乐、照片...
安卓车机11.0系统包,智能驾... 你有没有发现,最近你的安卓车机系统好像悄悄升级了呢?没错,就是那个安卓车机11.0系统包!这可不是一...
安卓系统最高到多少,从初代到最... 你有没有想过,你的安卓手机系统升级到哪一步了呢?是不是好奇安卓系统最高能到多少呢?别急,今天就来带你...