上一篇: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相关推荐

  1. Vue2.x 源码 - VNode渲染过程(update、patch)

    上一篇:Vue2.x 源码 - render 函数生成 VNode Vue 的渲染过程: 上一篇写了 render 函数生成 VNode 的过程,这一篇就来看看 VNode 是怎么渲染成真实 DOM ...

  2. 【手写 Vue2.x 源码】第二十八篇 - diff 算法-问题分析与 patch 优化

    一,前言 首先,对 6 月的更文内容做一下简单回顾: Vue2.x 源码环境的搭建 Vue2.x 初始化流程介绍 对象的单层.深层劫持 数组的单层.深层劫持 数据代理的实现 对象.数组数据变化的观测 ...

  3. 前端进阶-手写Vue2.0源码(三)|技术点评

    前言 今天是个特别的日子 祝各位女神女神节快乐哈 封面我就放一张杀殿的帅照表达我的祝福 哈哈 此篇主要手写 Vue2.0 源码-初始渲染原理 上一篇咱们主要介绍了 Vue 模板编译原理 它是 Vue ...

  4. Vue2.0源码解析——编译原理

    Vue2.0源码解析--编译原理 前言:本篇文章主要对Vue2.0源码的编译原理进行一个粗浅的分析,其中涉及到正则.高阶函数等知识点,对js的考察是非常的深的,因此我们来好好啃一下这个编译原理的部分. ...

  5. 【手写 Vue2.x 源码】第十八篇 - 根据 render 函数,生成 vnode

    一,前言 上篇,介绍了 render 函数的生成,主要涉及以下两点: 使用 with 对生成的 code 进行一次包装 将包装后的完整 code 字符串,通过 new Function 输出为 ren ...

  6. vue2.x源码解析(一)

    简介 本文以vue2.x框架作为分析,简单记录整个源码编译的过程.https://zhuanlan.zhihu.com/p/552685329 源码目录 src ├── compiler # 编译相关 ...

  7. Vue源码终笔-VNode更新与diff算法初探

    写完这个就差不多了,准备干新项目了. 确实挺不擅长写东西,感觉都是罗列代码写点注释的感觉,这篇就简单阐述一下数据变动时DOM是如何更新的,主要讲解下其中的diff算法. 先来个正常的html模板: & ...

  8. Vue2.x源码学习笔记-Vue实例的属性和方法整理

    还是先从浏览器直观的感受下实例属性和方法. 实例属性: 对应解释如下: vm._uid // 自增的id vm._isVue // 标示是vue对象,避免被observe vm._renderProx ...

  9. Vue2.0源码解析 - 知其然知其所以然之Vue.use

    前言 小伙伴们大家好.用过Vue的小伙伴都知道,在我们进行Vue开发时,避免不了会使用一些第三方的库,比如说ElementUI组件库.当我们导入好这些组件库后会执行一个Vue.use函数,然后把导进来 ...

最新文章

  1. Python 中常见的 TypeError 是什么?
  2. matlab 图像分割 提取人像_几种典型的图像处理技术(分类 目标提取 目标跟踪 语义分割 实例分割)...
  3. 利用奇异值分解(SVD)简化数据
  4. idea查看过期时间
  5. TypeScript 2.7 版本发布
  6. [css] 如何使用css实现鼠标跟随?
  7. 某些服务在未由其他服务或程序使用时将自动停止
  8. pyspark分类算法之多层感知机神经网络分类器模型实践【MLPClassifier】
  9. easyui 1.4.3 窗口创建或拖动超过父元素 边界 BUG 解决方法
  10. 三维空间长度温度数量_PET注塑成型温度过低会怎么样?
  11. gcc和vc的两点区别
  12. magento -- 添加中国省份列表
  13. STM32-GPIO的配置和使用
  14. (转)男人的梦想之野性篇 什么是真正的越野车
  15. Git出错,提示error: bad signature 0x00000000 fatal: index file corrupt(win10系统)
  16. 可解释机器学习- LIME模型讲解|interpretable machine learning-LIME
  17. 计算机综合布线考试试题A,综合布线试题A
  18. 网络安全知识之Cross-Site Request Forgery (CSRF) 简介
  19. 原型与原型链的学习理解
  20. 《代号:魂之刃2》- 身处黑暗的勇者游戏

热门文章

  1. C. Feast Coins(背包求方案数)
  2. 错误为0x8002801D 库没有注册 解决方案
  3. 支付宝小程序自定义状态栏,标题栏,标题栏文字。
  4. 微信小程序自定义顶部以及底部tabbar
  5. OAF: 怎样建立 LOV
  6. 统计完全二叉树的节点数-Java
  7. cute functions
  8. 贝医生创始人章骏:做出更适合于中国人的牙刷
  9. 高分子PEG:PEG nicotinic acid,MPEG Niacin,甲氧基聚乙二醇烟酸,水溶性强,可用于drμg的递送或生物测定开发
  10. Mac OS下给树莓派安装系统、无网线和外设配置WI-FI、SSH远程控制、VNC远程桌面(超详细~)