Vue2.x 源码 - render 函数生成 VNode
上一篇:Vue2.x 源码 - 编译过程(compile)
Vue 的渲染过程:
上一篇看了一下编译,编译之后返回了 render
函数,那么 render
又是如何生成 vnode 的呢?本文来看一下。
在 Vue 官方文档中介绍 render
函数 的第一个参数是 createElement
用来创建 VNode;
<div id="app">{{ message }}
</div>
用 render
函数表示:
render:function(createElement){return createElement('div',{attrs:{id:'app'}}, this.message)
}
再来看看 Vue 初始化的时候绑定在实例原型上的 _render
方法,在 src/core/instance/render.js
文件里:
//执行render函数生成vnode,以及异常处理Vue.prototype._render = function (): VNode {const vm: Component = thisconst { render, _parentVnode } = vm.$options//创建子组件的 vnode 执行normalizeScopedSlotsif (_parentVnode) {vm.$scopedSlots = normalizeScopedSlots(_parentVnode.data.scopedSlots,vm.$slots,vm.$scopedSlots)}// 设置父vnodevm.$vnode = _parentVnode// render selflet vnodetry {currentRenderingInstance = vm// 执行 render 函数,生成 vnodevnode = render.call(vm._renderProxy, vm.$createElement)} catch (e) {handleError(e, vm, `render`)// render函数出错// 开发环境渲染错误信息,生产环境返回之前的 vnode,以防止渲染错误导致组件空白if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {try {vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)} catch (e) {handleError(e, vm, `renderError`)vnode = vm._vnode}} else {vnode = vm._vnode}} finally {currentRenderingInstance = null}// 如果返回的数组只包含一个节点,转化一下if (Array.isArray(vnode) && vnode.length === 1) {vnode = vnode[0]}// 如果渲染函数出错,返回空vnodeif (!(vnode instanceof VNode)) {if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {warn('Multiple root nodes returned from render function. Render function ' +'should return a single root node.',vm)}vnode = createEmptyVNode()}// 设置父节点vnode.parent = _parentVnodereturn vnode}
这段代码主要是 vnode = render.call(vm._renderProxy, vm.$createElement)
这段代码拿到 vnode,可以看到传入的参数是 vm.$createElement
方法,这个方法是在 initRender
方法里面传入的 createElement
方法;最后的结果:vnode 是由 createElement
方法返回的;
文章目录
- 那么 vnode 到底是什么东西呢?
- createElement 方法
- 1、规范化子节点
- 2、创建 VNode 节点
- 3、创建组件节点
那么 vnode 到底是什么东西呢?
虚拟DOM(Virtual DOM)这个概念的产生是因为浏览器中频繁的对真实 DOM 进行操作是会产生性能问题的;虚拟 DOM 是用一个原生 js 对象去描述一个真实 DOM 节点;
由于直接操作 DOM 性能低,但是 js 层的操作效率高,可以将 DOM 操作转化成对象操作,最终通过 diff 算法比对差异进行更新 DOM (减少了对真实 DOM 的操作)。虚拟 DOM 不依赖真实平台环境从而也可以实现跨平台;
在 Vue.js 中,虚拟 DOM 是⽤ VNode 这么⼀个 Class 去描述,它是定义在 src/core/vdom/vnode.js
中的:
export default class VNode {tag: string | void;data: VNodeData | void;children: ?Array<VNode>;text: string | void;elm: Node | void;ns: string | void;context: Component | void; // rendered in this component's scopekey: string | number | void;componentOptions: VNodeComponentOptions | void;componentInstance: Component | void; // component instanceparent: VNode | void; // component placeholder node// strictly internalraw: boolean; // contains raw HTML? (server only)isStatic: boolean; // hoisted static nodeisRootInsert: boolean; // necessary for enter transition checkisComment: boolean; // empty comment placeholder?isCloned: boolean; // is a cloned node?isOnce: boolean; // is a v-once node?asyncFactory: Function | void; // async component factory functionasyncMeta: Object | void;isAsyncPlaceholder: boolean;ssrContext: Object | void;fnContext: Component | void; // real context vm for functional nodesfnOptions: ?ComponentOptions; // for SSR cachingdevtoolsMeta: ?Object; // used to store functional render context for devtoolsfnScopeId: ?string; // functional scope id supportconstructor (tag?: string,data?: VNodeData,children?: ?Array<VNode>,text?: string,elm?: Node,context?: Component,componentOptions?: VNodeComponentOptions,asyncFactory?: Function) {this.tag = tag //当前标签名this.data = data //当前节点数据this.children = children //当前节点的子节点this.text = text //当前节点文本this.elm = elm //当前虚拟节点对应的真实节点this.ns = undefined //当前节点的名字空间this.context = context //当前节点的编译作用域this.fnContext = undefined //用于功能节点的真实上下文vmthis.fnOptions = undefined //SSR缓存this.fnScopeId = undefined //功能范围id支持this.key = data && data.key //节点的key属性,被当作节点的标志,用以优化this.componentOptions = componentOptions //组件的option选项this.componentInstance = undefined //当前节点对应的组件的实例this.parent = undefined //当前节点的父节点this.raw = false //是否为原生HTML或只是普通文本this.isStatic = false //是否是静态节点this.isRootInsert = true //是否作为根节点插入this.isComment = false //是否是注释节点this.isCloned = false //是否是克隆节点this.isOnce = false //是否是v-once指令this.asyncFactory = asyncFactory //异步组件的工厂方法this.asyncMeta = undefined //异步源this.isAsyncPlaceholder = false //是否异步的预赋值}// DEPRECATED: alias for componentInstance for backwards compat./* istanbul ignore next */get child (): Component | void {return this.componentInstance}
}
这里有很多的属性,其中最重要的属性只有几个:tag、data、children 和 key。其余很多属性只是在 Vue 中为适用不同的场景,额外添加的。
createElement 方法
在 src/core/vdom/create-element.js
文件里:
export function createElement (context: Component,tag: any,data: any,children: any,normalizationType: any,alwaysNormalize: boolean
): VNode | Array<VNode> {//data是数组或者原始类型,说明当前参数没有传递data,所以需要将参数重载if (Array.isArray(data) || isPrimitive(data)) {normalizationType = childrenchildren = datadata = undefined}//确定参数使用哪一个 normalization,根据render初始化的时候传入的boolean值决定if (isTrue(alwaysNormalize)) {normalizationType = ALWAYS_NORMALIZE}return _createElement(context, tag, data, children, normalizationType)
}
对于没有传递 data 的情况这里多说一下,因为很容易误解:第三个参数 data 是可以不传的
//没有传递data
creatElement(this, 'div', 'hello', 1, false);
|
|
//重载后
creatElement(this, 'div', undefined, 'hello', 1, false);
将 data 设置为 undefined,其他的参数向后移动一位,这样形参和实参就能一 一对应起来了;
createElement
⽅法实际上是对 _createElement
⽅法的封装,它允许传⼊的参数更加灵活,在处理这些参数后,调⽤真正创建 VNode 的函数 _createElement
;
_createElement
函数的入参:
context:VNode 当前上下文环境;
tag:标签,可以是正常的HTML元素标签,也可以是Component组件;
data:VNode的数据,其类型为VNodeData;
children:VNode的子节点;
normalizationType:children子节点规范化类型;
具体代码:
export function _createElement (context: Component,tag?: string | Class<Component> | Function | Object,data?: VNodeData,children?: any,normalizationType?: number
): VNode | Array<VNode> {//data是响应式数据,创建空vnodeif (isDef(data) && isDef((data: any).__ob__)) {process.env.NODE_ENV !== 'production' && warn(`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +'Always create fresh vnode data objects in each render!',context)return createEmptyVNode()}//检测data中是否有is属性,是的话tag替换为is指向的内容,处理动态组件if (isDef(data) && isDef(data.is)) {tag = data.is}// tag如果为空,创建空虚拟节点if (!tag) {return createEmptyVNode()}// data 中的key如果定义了必须是string、number、boolean以及 symbolif (process.env.NODE_ENV !== 'production' &&isDef(data) && isDef(data.key) && !isPrimitive(data.key)) {if (!__WEEX__ || !('@binding' in data.key)) {warn('Avoid using non-primitive value as key, ' +'use string/number value instead.',context)}}// 支持单函数子函数作为默认作用域槽if (Array.isArray(children) &&typeof children[0] === 'function') {data = data || {}data.scopedSlots = { default: children[0] }children.length = 0}//处理children的两种模式if (normalizationType === ALWAYS_NORMALIZE) {children = normalizeChildren(children)} else if (normalizationType === SIMPLE_NORMALIZE) {children = simpleNormalizeChildren(children)}let vnode, ns//tag是stringif (typeof tag === 'string') {let Ctor//获取tag的名字空间ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)//判断是否是保留的标签if (config.isReservedTag(tag)) {// tag 上有.native修饰符,发出警告 :v-on的.native修饰符只在组件上有效,但在<${tag}>上使用过if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {warn(`The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,context)}//如果是保留的标签则创建一个相应节点vnode = new VNode(config.parsePlatformTagName(tag), data, children,undefined, undefined, context)} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {//如果tag对应的是组件名,创建组件vnode = createComponent(Ctor, data, context, children, tag)} else {//未知或未列出的名称空间元素在运行时进行检查,因为当其父元素将子元素规范化时,可能会给它分配一个名称空间vnode = new VNode(tag, data, children,undefined, undefined, context)}} else {//当 render: h => h(App) 时传入的是组件则tag为对象 走此逻辑// direct component options / constructorvnode = createComponent(tag, data, context, children)}//vnode是数组直接返回;if (Array.isArray(vnode)) {return vnode} else if (isDef(vnode)) {//不是数组但是不为空,如果有名字空间,则递归所有子节点应用该名字空间,if (isDef(ns)) applyNS(vnode, ns)//当在槽节点上使用像:style和:class这样的深度绑定时,必须确保父节点重新呈现if (isDef(data)) registerDeepBindings(data)return vnode} else {//为空则创建一个空vnode;return createEmptyVNode()}
}
通过检测 data 中是否有 is 属性,是的话 tag 替换为 is 指向的内容,处理动态组件;
_createElement
主要做两件事情:规范化子节点和创建 VNode 节点,接下来我们围绕这两个方面来详细介绍。
1、规范化子节点
虚拟 DOM 是一个树形结构,每一个节点都应该是 VNode 类型,但是 children 参数又是任意类型的,所以如果有子节点,我们需要把它进行规范化成 VNode 类型,如果没有子节点,那么 children 就是 undefined。下面就看看子节点的规范,在 src/core/vdom/helpers/normalize-children.js
文件中:
1、simpleNormalizeChildren
export function simpleNormalizeChildren (children: any) {for (let i = 0; i < children.length; i++) {if (Array.isArray(children[i])) {return Array.prototype.concat.apply([], children)}}return children
}
simpleNormalizeChildren ⽅法调⽤场景是 render 函数当函数是编译⽣成的。理论上编译⽣成的 children 都已经是 VNode 类型的,但这⾥有⼀个例外,就是 functional component 函数式组件 返回的是⼀个数组⽽不是⼀个根节点,所以会通过 Array.prototype.concat ⽅法把整个 children 数组降维,让它的深度只有⼀层。
2、normalizeChildren
export function normalizeChildren (children: any): ?Array<VNode> {return isPrimitive(children)? [createTextVNode(children)]: Array.isArray(children)? normalizeArrayChildren(children): undefined
}
normalizeChildren
⽅法的调⽤场景有 2 种,⼀个场景是 render 函数是⽤户⼿写的,当 children 只有⼀个节点的时候,Vue.js 从接⼝层⾯允许⽤户把 children 写成基础类型⽤来创建单 个简单的⽂本节点,这种情况会调⽤ createTextVNode
创建⼀个⽂本节点的 VNode;另⼀个场景是 当编译 template、slot 、 v-for
的时候会产⽣嵌套数组的情况,会调⽤ normalizeArrayChildren
⽅法, 接下来看⼀下它的实现:
function normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> {const res = []let i, c, lastIndex, last//遍历children 获取单个节点判断是不是数组是则递归for (i = 0; i < children.length; i++) {c = children[i]if (isUndef(c) || typeof c === 'boolean') continuelastIndex = res.length - 1last = res[lastIndex]// nestedif (Array.isArray(c)) {if (c.length > 0) {c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)// 合并相邻文本节点if (isTextNode(c[0]) && isTextNode(last)) {res[lastIndex] = createTextVNode(last.text + (c[0]: any).text)c.shift()}res.push.apply(res, c)}} else if (isPrimitive(c)) {if (isTextNode(last)) {// 合并相邻的两个textres[lastIndex] = createTextVNode(last.text + c)} else if (c !== '') {// 将primitive转换为vnoderes.push(createTextVNode(c))}} else {if (isTextNode(c) && isTextNode(last)) {//合并相邻文本节点res[lastIndex] = createTextVNode(last.text + c.text)} else {// 嵌套数组子数组的默认键(可能是由v-for生成的)if (isTrue(children._isVList) &&isDef(c.tag) &&isUndef(c.key) &&isDef(nestedIndex)) {c.key = `__vlist${nestedIndex}_${i}__`}res.push(c)}}}return res
}
nestedIndex 表示嵌套索引,循环 children 节点,判断每一个子节点是不是数组类型,是则递归调用 normalizeArrayChildren
方法;如果是基础类型调用 createTextVNode
方法转换成 VNode 类型,然后推到数组中去;如果已经是 VNode 类型了直接推进数组即可;
在遍历过程中,如果存在连续的两个 text 节点,则会把他们合并成一个 text 节点;
2、创建 VNode 节点
回到 createElement
函数,规范化 children 后,接下来会去创建⼀个 VNode 的实例:
if (typeof tag === 'string') {let Ctorns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)if (config.isReservedTag(tag)) {// platform built-in elementsif (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn)) {warn(`The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,context)}vnode = new VNode(config.parsePlatformTagName(tag), data, children,undefined, undefined, context)} else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {// componentvnode = createComponent(Ctor, data, context, children, tag)} else {// unknown or unlisted namespaced elements// check at runtime because it may get assigned a namespace when its// parent normalizes childrenvnode = new VNode(tag, data, children,undefined, undefined, context)}} else {// direct component options / constructorvnode = createComponent(tag, data, context, children)}
这⾥先对 tag 做判断,如果是 string 类型,则接着判断如果是内置的⼀些节点,则直接创建⼀个 普通 VNode,如果是为已注册的组件名,则通过 createComponent
创建⼀个组件类型的 VNode,否 则创建⼀个未知的标签的 VNode。 如果是 tag ⼀个 Component 类型,则直接调⽤ createComponent
创建⼀个组件类型的 VNode 节点;
3、创建组件节点
这里创建组件类型的 VNode 节点 在 src/core/vdom/create-component.js
文件中:
export function createComponent (Ctor: Class<Component> | Function | Object | void,data: ?VNodeData,context: Component,children: ?Array<VNode>,tag?: string
): VNode | Array<VNode> | void {//判断节点tag不存在直接返回if (isUndef(Ctor)) {return}//创建子类构造器函数const baseCtor = context.$options._base// 普通选项对象:将其转换为构造函数if (isObject(Ctor)) {Ctor = baseCtor.extend(Ctor)}// 如果在此阶段它不是构造函数或异步组件工厂,则拒绝。if (typeof Ctor !== 'function') {if (process.env.NODE_ENV !== 'production') {warn(`Invalid Component definition: ${String(Ctor)}`, context)}return}// 异步组件let asyncFactoryif (isUndef(Ctor.cid)) {asyncFactory = CtorCtor = resolveAsyncComponent(asyncFactory, baseCtor)if (Ctor === undefined) {// 返回async组件的占位符节点,该节点呈现为注释节点,但保留该节点的所有原始信息。这些信息将用于异步服务器渲染和水合。return createAsyncPlaceholder(asyncFactory,data,context,children,tag)}}data = data || {}// 解析构造函数选项,以防在创建组件构造函数后应用全局mixinresolveConstructorOptions(Ctor)// 将组件v-model 数据转换为props和eventsif (isDef(data.model)) {transformModel(Ctor.options, data)}//提取propsconst propsData = extractPropsFromVNodeData(data, Ctor, tag)// 功能组件if (isTrue(Ctor.options.functional)) {return createFunctionalComponent(Ctor, propsData, data, context, children)}// 提取侦听器,因为需要将它们视为子组件侦听器而不是DOM侦听器const listeners = data.on// 用带有.native修饰符的监听器替换,以便在父组件补丁期间处理它。data.on = data.nativeOnif (isTrue(Ctor.options.abstract)) {// 抽象组件只保留 props & listeners & slotconst slot = data.slotdata = {}if (slot) {data.slot = slot}}// 将组件管理钩子安装到占位符节点上installComponentHooks(data)// return a placeholder vnodeconst name = Ctor.options.name || tagconst vnode = new VNode(`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,data, undefined, undefined, undefined, context,{ Ctor, propsData, listeners, tag, children },asyncFactory)// Weex specific: invoke recycle-list optimized @render function for// extracting cell-slot template.// https://github.com/Hanks10100/weex-native-directive/tree/master/componentif (__WEEX__ && isRecyclableComponent(vnode)) {return renderRecyclableComponentTemplate(vnode)}return vnode
}
总共分为三个步骤:构建子类构造器函数、安装组件钩子函数、实例化 vnode;
1、构建子类构造器函数
const baseCtor = context.$options._base// 普通选项对象:将其转换为构造函数if (isObject(Ctor)) {Ctor = baseCtor.extend(Ctor)}
这里的 context 从入参追溯,是 Vue 实例;之前的 initGlobalAPI
方法中有这样一段代码 Vue.options._base = Vue
,在参数合并的时候用 mergeOptions
方法把 options 扩展到了 vm.$options
上,这样 baseCtor 就是 Vue 实例本身;baseCtor.extend(Ctor)
实际上是 Vue.extend(Ctor)
;关于 Vue.extend
可以参考:(Vue2.x 源码 - 初始化:全局API);将 Ctor 构建成子类构造器函数;
2、安装组件钩子函数
installComponentHooks(data)
function installComponentHooks (data: VNodeData) {const hooks = data.hook || (data.hook = {})//遍历自定义的hooksfor (let i = 0; i < hooksToMerge.length; i++) {const key = hooksToMerge[i]const existing = hooks[key]const toMerge = componentVNodeHooks[key]if (existing !== toMerge && !(existing && existing._merged)) {hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge}}
}
const componentVNodeHooks = {//初始化时触发init (vnode: VNodeWithData, hydrating: boolean): ?boolean {if (vnode.componentInstance &&!vnode.componentInstance._isDestroyed &&vnode.data.keepAlive) {// kept-alive components, treat as a patchconst mountedNode: any = vnode // work around flowcomponentVNodeHooks.prepatch(mountedNode, mountedNode)} else {const child = vnode.componentInstance = createComponentInstanceForVnode(vnode,activeInstance)child.$mount(hydrating ? vnode.elm : undefined, hydrating)}},//patch之前触发prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {const options = vnode.componentOptionsconst child = vnode.componentInstance = oldVnode.componentInstanceupdateChildComponent(child,options.propsData, // updated propsoptions.listeners, // updated listenersvnode, // new parent vnodeoptions.children // new children)},//插入到DOM时触发insert (vnode: MountedComponentVNode) {const { context, componentInstance } = vnodeif (!componentInstance._isMounted) {componentInstance._isMounted = truecallHook(componentInstance, 'mounted')}if (vnode.data.keepAlive) {if (context._isMounted) {// vue-router#1212// During updates, a kept-alive component's child components may// change, so directly walking the tree here may call activated hooks// on incorrect children. Instead we push them into a queue which will// be processed after the whole patch process ended.queueActivatedComponent(componentInstance)} else {activateChildComponent(componentInstance, true /* direct */)}}},//节点移除之前触发destroy (vnode: MountedComponentVNode) {const { componentInstance } = vnodeif (!componentInstance._isDestroyed) {if (!vnode.data.keepAlive) {componentInstance.$destroy()} else {deactivateChildComponent(componentInstance, true /* direct */)}}}
}
const hooksToMerge = Object.keys(componentVNodeHooks)
在installComponentHooks
方法执行的时候,遍历componentVNodeHooks
方法里定义的 hooks 对象的属性,然后会把这些 hooks 合并到 data.hook
上面,方便后面使用;如果有相同的 hook,则会执行mergeHook
方法来合并,mergeHook
方法的定义如下:
function mergeHook (f1: any, f2: any): Function {const merged = (a, b) => {f1(a, b)f2(a, b)}merged._merged = truereturn merged
}
合并就是把 data.hook
里面的钩子函数在componentVNodeHooks
里面定义的钩子函数后面执行;
3、实例化 VNode
const name = Ctor.options.name || tag
const vnode = new VNode(`vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,data, undefined, undefined, undefined, context,{ Ctor, propsData, listeners, tag, children },asyncFactory
)
通过 new VNode
实例化⼀个 vnode 并返回。
总结:render
函数主要是调用 createElement
方法包装的 _createElement
方法,该对子节点进行规范化之后创建生成 vnode, 然后会调用 vm._update
⽅法,执⾏ patch
函数,将 vnode 渲染到真实 DOM 节点上去;
下一篇:Vue2.x 源码 - VNode渲染过程(update、patch)
Vue2.x 源码 - render 函数生成 VNode相关推荐
- Vue2.x 源码 - VNode渲染过程(update、patch)
上一篇:Vue2.x 源码 - render 函数生成 VNode Vue 的渲染过程: 上一篇写了 render 函数生成 VNode 的过程,这一篇就来看看 VNode 是怎么渲染成真实 DOM ...
- 【手写 Vue2.x 源码】第二十八篇 - diff 算法-问题分析与 patch 优化
一,前言 首先,对 6 月的更文内容做一下简单回顾: Vue2.x 源码环境的搭建 Vue2.x 初始化流程介绍 对象的单层.深层劫持 数组的单层.深层劫持 数据代理的实现 对象.数组数据变化的观测 ...
- 前端进阶-手写Vue2.0源码(三)|技术点评
前言 今天是个特别的日子 祝各位女神女神节快乐哈 封面我就放一张杀殿的帅照表达我的祝福 哈哈 此篇主要手写 Vue2.0 源码-初始渲染原理 上一篇咱们主要介绍了 Vue 模板编译原理 它是 Vue ...
- Vue2.0源码解析——编译原理
Vue2.0源码解析--编译原理 前言:本篇文章主要对Vue2.0源码的编译原理进行一个粗浅的分析,其中涉及到正则.高阶函数等知识点,对js的考察是非常的深的,因此我们来好好啃一下这个编译原理的部分. ...
- 【手写 Vue2.x 源码】第十八篇 - 根据 render 函数,生成 vnode
一,前言 上篇,介绍了 render 函数的生成,主要涉及以下两点: 使用 with 对生成的 code 进行一次包装 将包装后的完整 code 字符串,通过 new Function 输出为 ren ...
- vue2.x源码解析(一)
简介 本文以vue2.x框架作为分析,简单记录整个源码编译的过程.https://zhuanlan.zhihu.com/p/552685329 源码目录 src ├── compiler # 编译相关 ...
- Vue源码终笔-VNode更新与diff算法初探
写完这个就差不多了,准备干新项目了. 确实挺不擅长写东西,感觉都是罗列代码写点注释的感觉,这篇就简单阐述一下数据变动时DOM是如何更新的,主要讲解下其中的diff算法. 先来个正常的html模板: & ...
- Vue2.x源码学习笔记-Vue实例的属性和方法整理
还是先从浏览器直观的感受下实例属性和方法. 实例属性: 对应解释如下: vm._uid // 自增的id vm._isVue // 标示是vue对象,避免被observe vm._renderProx ...
- Vue2.0源码解析 - 知其然知其所以然之Vue.use
前言 小伙伴们大家好.用过Vue的小伙伴都知道,在我们进行Vue开发时,避免不了会使用一些第三方的库,比如说ElementUI组件库.当我们导入好这些组件库后会执行一个Vue.use函数,然后把导进来 ...
最新文章
- Python 中常见的 TypeError 是什么?
- matlab 图像分割 提取人像_几种典型的图像处理技术(分类 目标提取 目标跟踪 语义分割 实例分割)...
- 利用奇异值分解(SVD)简化数据
- idea查看过期时间
- TypeScript 2.7 版本发布
- [css] 如何使用css实现鼠标跟随?
- 某些服务在未由其他服务或程序使用时将自动停止
- pyspark分类算法之多层感知机神经网络分类器模型实践【MLPClassifier】
- easyui 1.4.3 窗口创建或拖动超过父元素 边界 BUG 解决方法
- 三维空间长度温度数量_PET注塑成型温度过低会怎么样?
- gcc和vc的两点区别
- magento -- 添加中国省份列表
- STM32-GPIO的配置和使用
- (转)男人的梦想之野性篇 什么是真正的越野车
- Git出错,提示error: bad signature 0x00000000 fatal: index file corrupt(win10系统)
- 可解释机器学习- LIME模型讲解|interpretable machine learning-LIME
- 计算机综合布线考试试题A,综合布线试题A
- 网络安全知识之Cross-Site Request Forgery (CSRF) 简介
- 原型与原型链的学习理解
- 《代号:魂之刃2》- 身处黑暗的勇者游戏