vue源码阅读---vm实例上的属性
一、vnode
vnode有那些类型?
1.文本类型
2.注释类型
3.组件类型
vnode.$attrs:
vnode.$slot:组件占位符包括的子节点,其实就是插槽的内容;组价开始标签和组件结束标签里包裹的内容;注意和vnode.children的区别;
vnode.children:普通元素包括的子节点;
渲染vnode.parent = vm.$options._parentVnode = vm.$vnode 是子组件的占位符vnode;
在子组件实例化时,会有一个const child = vnode.componentInstance = createComponentInstanceForVnode;所以组件vnode的componentInsatcne就有值了。
vnode.isMounted:组件是否已经patch成功;
vnode.componentOptions: 在组件实例化时传入的第七个参数;
vnode.elm:渲染vnode创建的真实dom
二、vm实例
vm._isVue // 标示是vue对象,避免被observe; observe方法在判断是会判断一个对象的的_isVue属性是否为false,如果为true说明是一个vm实例,则不进行响应式;
vm._renderProxy // 说白了,主要是给vm做个代理,然后赋值给vm_renderProxy,vm._renderProxy后续会在调用_render函数用;
vm._self =vm// 指向当前vm实例
vm.$root // 指向根vm实例
vm.$children // 当前组件的子组件实例数组
vm.$slot://一个对象,对象里会有具名插槽名字作为key,value是当前组件名字为key的插槽vnode;对象里会有一个叫default的key,value是当前组件的没有名字的插槽元素;
vm.$chilren和vm.$slot的区别:第一vm.$chilren装的是子组件实例 vm.$slot装的是插槽的vnode;第二:vm.$chilren数组的个数肯定比vm.$slot多,为什么?因为就以el-tab举例,el-tab元素本生除了几个插槽<slot></slot>占位组件,肯定内部还有其他组件比如el-tab-Nav子组件;插槽元素在el-tab渲染过程中,也会通过_t方法resolveSlot方法替换<slot></slot>占位组件vm.$chilren多;
vm.$refs 一个对象,持有注册过 ref
attribute 的所有 DOM 元素和组件实例。
- vm._watcher = null //组件实例对应的渲染watcher 实例对象。
- vm._computedWatchers // vue会给我们自己定义的计算属性的每个key都创建一个watcher对象,vm._computedWatchers 就是来保存这些计算属性创建的计算watcher;
- vm._watchers // 组件实例包含的watchers、可能有渲染watchers、计算watcher、侦听watchers等;
vm._inactive = null 表示keep-alive中组件状态,如被激活,该值为false,反之为true。
vm._directInactive = false
vm._isMounted = false // 当前实例是否完成挂载(对应生命周期图示中的mounted)
vm._isDestroyed = false // 标识是否已销毁
vm._isBeingDestroyed = false // 当前实例是否正在被销毁,还没有销毁完成(介于生命周期图示中deforeDestroy和destroyed之间)。
vm._events //父组件绑定在当前组件上的事件。
vm._hasHookEvent // 标示是否有hook:开头的事件。该属性表示父组件是否通过"@hook:"把钩子函数绑定在当前组件上。在后续调用钩子函数时,也会emit父组件在子组件占位符定义的hook:开头的事件
vm.$vnode // 当前自定义组件在父组件中的占位符vnode,等同于vm.$options._parentVnode // the placeholder node in parent tree
vm._vnode
和 vm.$vnode
的关系就是一种父子关系,用代码表达就是 vm._vnode.parent = vm.$vnode
vm.$slots // 定义在父组件中的slots,是个对象键为name,值为响应的数组
vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject 如果占位符vnode存在插槽数据则使用它,否则传递一个空对象;
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)// 使用compile模块编译模板生成的render函数使用的创建vnode的方法
vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)// 用户自己写render方法时,传入的参数
vm.$attr //父组件给子组件占位符Vnode上写的属性
vm.$listener//父组件给子组件占位符Vnode上写的监听事件;
- vm._props // 被observe的存储了我们自定义props数据的一个对象; 实际上访问vm.prop名字时会通过代理访问vm._props.prop名字;
- vm.方法名字 //指向自定义的methods里定义的方法
- vm._data // 被observe的存储data数据的对象; 实际上访问vm.data名字时会通过代理访问vm._datas.data名字;
- vm.计算属性名 // target是vm,key是计算属性名,sPD.get是计算属性的返回值;注意通过vm访问计算属性时,它的实现可不是prop和data的代理;它是直接把子组件的计算属性挂载到Sub.prototype上了。
- vm._computedWatchers // vue会给我们自己定义的计算属性的每个key都创建一个watcher对象,vm._computedWatchers 就是来保存这些计算属性创建的计算watcher;
vm.$el // 当前组件对应的根元素 //当前组件的真实dom吧,不清除,后面看吧,目前vm.$el = this.__patch()__ = vnode.elm;但是目前不知道vnode.elm是什么时候得到值得;
目前在mountComponent阶段,vm.$el = el;那么根实例中,el就是挂载#app的element元素;
子组件中调用mountComponet时el为什么?为vnode.elm;那么这个vnode是占位符vnode还是什么?我看了下,是组件额位符vnode即vm.$vnode,子组件的elm基本都是undefined;
三、Vue.prototype
在instance/index.js文件中,给Vue.prototype定义了如下属性:
2
src/platforms/web/runtime/index.js
vm.$watch // 观察 Vue 实例上的一个表达式或者一个函数计算结果的变化
vm.$set // 向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的
vm.$delete // 删除对象的 property。如果对象是响应式的,确保删除能触发更新视图
vm.$on // 监听当前实例上的自定义事件
vm.$once // 监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除
vm.$off // 移除自定义事件监听器
vm.$emit // 触发当前实例上的事件。附加参数都会传给监听器回调
vm.$mount // 手动地挂载一个未挂载的实例
vm.$beforcepdate// 迫使 Vue 实例重新渲染
vm.$nextTick // 将回调延迟到下次 DOM 更新循环之后执行
vm.$destory //
四、vm.$options
declare type ComponentOptions = { data: Object | Function | void; // 传入的data数据 props?: { [key: string]: PropOptions }; // props传入的数据 propsData?: ?Object; // 对于自定义组件,父级通过`props`传过来的数据 computed?: { // 传入的计算属性 [key: string]: Function | { get?: Function; set?: Function; cache?: boolean } }; methods?: { [key: string]: Function }; // 传入的方法 watch?: { [key: string]: Function | string }; // 传入的watch // DOM el?: string | Element; // 传入的el字符串 template?: string; // 传入的模板字符串 render: (h: () => VNode) => VNode; // 传入的render函数 renderError?: (h: () => VNode, err: Error) => VNode; staticRenderFns?: Array<() => VNode>; // 钩子函数 beforeCreate?: Function; created?: Function; beforeMount?: Function; mounted?: Function; beforeUpdate?: Function; updated?: Function; activated?: Function; deactivated?: Function; beforeDestroy?: Function; destroyed?: Function; // assets directives?: { [key: string]: Object }; // 指令 components?: { [key: string]: Class<Component> }; // 子组件的定义 transitions?: { [key: string]: Object }; filters?: { [key: string]: Function }; // 过滤器 // context provide?: { [key: string | Symbol]: any } | () => { [key: string | Symbol]: any }; inject?: { [key: string]: string | Symbol } | Array<string>; // component v-model customization model?: { prop?: string; event?: string; }; // misc parent?: Component; // 父组件实例 mixins?: Array<Object>; // mixins传入的数据 name?: string; // 当前的组件名 extends?: Class<Component> | Object; // extends传入的数据 delimiters?: [string, string]; // 模板分隔符 // 私有属性,均为内部创建自定义组件的对象时使用 _isComponent?: true; // 是否是组件 _propKeys?: Array<string>; // props传入对象的键组成的数组 _parentVnode?: VNode; // 当前组件,在父组件中的占位符VNode对象 _parentListeners?: ?Object; // 当前组件,在父组件上绑定的事件,和vm_event差不多; _renderChildren?: ?Array<VNode>; // 父组件中定义在当前元素内的子元素的VNode数组(slot) _componentTag: ?string; // 自定义标签名 _scopeId: ?string; _base: Class<Component>; // Vue _parentElm: ?Node; // 当前自定义组件的父级dom结点 _refElm: ?Node; // 当前元素的nextSlibing元素,即当前dom要插入到_parentElm结点下的_refElm前 }
五、Vue的静态属性和静态方法
5.1静态属性
- Vue.options:包含了很多东西,比如使用Vue.component方法注册的全局组件,会添加在Vue.options里;Vue.mixin混入的东西,也会添加到Vue.options;
5.2静态方法
- Vue.component:全局组件注册方法
- Vue.direactive
- Vue.filter
- Vue.mixin:混入的东西,后面会添加到Vue.options里;
- Vue.set()
- Vue.del 和Vue.set是相反的道理;
六、钩子函数
组件实例的钩子函数beforeCreated,created,beforeMounted,mounted
组件vnode的Hooks:
init, prepatch,inserted,destory
modules里定义的属性钩子函数:
core目录下所有平台公有的:
- 指令的 create钩子、update钩子、destory钩子
- ref属性 create钩子、update钩子、destory钩子
web平台特有的:
- v-mode的 insert钩子和updateComponent钩子
- v-show的 bind钩子、update钩子、unbind钩子
- attr、class、style、domPorp、event、transition的 create钩子和update钩子;
cbs对象,会有属性create、active、update、remove、destory这5个属性;
每个属性比如cbs.create是一个数组,这个数组的元素就是modeules(平台特有modules+公有modules)里的每个文件的create钩子函数;
所以当我们执行invokeCreateHook时,是把modeules里的所有create钩子函数都执行一遍;
问题1:
什么时候把自定义指令的insert钩子函数添加到insertVnodeQueue中的?
在调用指令的create钩子函数时,把指令insert钩子函数添加到普通vnode.data.hook.insert中;
然后等到普通vnode在patch完成后,会调用invokeINsertHooks,会把insertQueue里的每个vnode取出,不管是普通vnode还是组件vnode都会调用它的vnode.data.hook.insert方法;
问题2:
自定义指令在普通元素上和组件上,生成的render函数有什么不同?
1.在普通元素上,我们可以看到有directives属性,和v-model是一样的。
我感觉,data属性有个directives属性表示会调用指令的钩子函数;仅此而言;
毕竟内置指令v-model在编译过程是直接被转化为domProps和on属性的,多的directives说白了就是执行下v-model的insert钩子函数,添加一个compositionStart事件;
2.在组件上,我们看到没什么区别,也有directives属性,说明也会去调用指令的钩子函数;
那么,为什么,v-model在组件上定义的时候,没有directives属性?而是直接被编译成了model属性?可能v-model内置指令在组件上不需要调用v-model的insert钩子函数吧;
<div> <h1 v-auth='"authTest"' class='firstH'>{{message}}</h1> <child v-auth='"authTestChild"' class="childComp"></child> </div> (function anonymous( ) { with (this) { return _c('div', [_c('h1', { directives: [{ name: "auth", rawName: "v-auth", value: ("authTest"), expression: "\"authTest\"" }], staticClass: "firstH" }, [_v(_s(message))]), _v(" "), _c('child', { directives: [{ name: "auth", rawName: "v-auth", value: ("authTestChild"), expression: "\"authTestChild\"" }], staticClass: "childComp" }) ], 1) } })
区别:
自定义指令需要自身通过Vue.direcitves()方法,把自身指令名和指令回调函数注册到Vue.options.direactives上;
而web平台内置指令v-model,v-show等在src/platform/runtime/index.js文件夹里,就已经通过extend(Vue.options.directives, platformDirectives)方法注册到Vue.options.direactives上了。
流程:
1.拓展到Vue.options.direacitves上
2.在本元素创建完成,本元素的createChildren也完成,但是本元素还没patch到父元素上时,调用invokeCreateHooks,调用本指令的create钩子函数;
本指令的create钩子函数是vue自己提供的,不需要用户写;
4.create钩子函数,会调用我们写得bind函数,然后把我们写得insert钩子函数使用mergeVnodeHooks赋值给vnode.data.hook.insert上
5.等到元素patch到父元素上时,会调用invokeInsertHooks,这个时候就是调用vnode.data.hook.insert,这个时候我们手写的insert函数就会执行了。