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.componentOptions.Ctor:组件构造函数
vnode.componentInstacne:指向组件vnode的实例;

 

vnode.elm:渲染vnode创建的真实dom 

 

 

 

 

二、vm实例

vm._uid 表示第几个实例化的vm实例或者子组件实例;

vm._isVue // 标示是vue对象,避免被observe;  observe方法在判断是会判断一个对象的的_isVue属性是否为false,如果为true说明是一个vm实例,则不进行响应式;

vm.constructor 因为vm是Vue的实例对象,所以vm.constructor等于Vue.prototype.constructor,所以指向Vue函数,即Vue;

vm._renderProxy // 说白了,主要是给vm做个代理,然后赋值给vm_renderProxy,vm._renderProxy后续会在调用_render函数用;

vm._self  =vm// 指向当前vm实例

 

vm.$parent = parent = activeInstance // 用于自定义子组件中,指向第一个非抽象父组件的实例(keeplive是抽象父组件)

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   // 当前组件的渲染vnode(the root of the child tree) vm._vnode 和 vm.$vnode 的关系就是一种父子关系,用代码表达就是 vm._vnode.parent = vm.$vnode
                 // vm._vnode.parent = 渲染vnode.parent = vm.$vnode =占位符vnode;
 
vm._staticTrees // 当前组件模板内分析出的静态内容的render函数数组 // v-once cached trees

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函数就会执行了。

 

posted @ 2022-08-17 21:52  Eric-Shen  阅读(419)  评论(0编辑  收藏  举报