千层套路 - Vue 3.0 初始化源码探秘

关注若川视野, 回复"pdf" 领取资料,回复"1",可加群长期交流学习

刘崇桢,微医云服务团队前端工程师,左手抱娃、右手持家的非典型码农。

9 月初 Vue.js 3.0 正式发布,代号 "One Piece"。大秘宝都摆到眼巴前了,再不扒拉扒拉就说不过去了。那我们就从初始化开始。

目标:

  • 弄清楚 createApp(App).mount("#app") 到底做了什么

  • 弄清楚 Vue3.0 的初始化渲染是怎么样的过程

能收获到什么:

  • 了解 Vue3.0 的初始化过程

  • 介绍一个阅读 Vue3.0 源码的入口和方向

先跑起来

vue-next 代码克隆到本地,打开 package.jsonscripts dev 末尾加上 --sourcemap

然后  yarn devvue 目录下的  dist  打包出了一份  vue.global.js 和相应的 sourcemap 文件。这样方便我们一步一步调试代码,查看程序在 call Stack 中的每一步调用。

查看 vue 官方给出的 demo,发现 vue 的使用分为 classiccomposition,我们先用 classic 方式,实现一个最简单的 demo。

const app = {data () {return {counter: 1}}
}
Vue.createApp(app).mount("#app")

ok,页面跑起来了。我们就在这段代码打个断点,然后一步一步的调试,观察createApp(App).mount("#app")到底做了什么,了解Vue3.0的初始化过程。

在这之前,简单了解一下整体的背景,我们这次主要涉及到 runtime 运行时的代码。

runtime-dom

我们先跟着代码进入:createApp(App).mount("#app");

这个 createApp() 来自 runtime-dom,我们通过这个图可以看到他大致做的事情:return 了一个注册了 mount 方法 app。这样我们的 demo 至少能跑起来不报错。

createApp 调用了 ensureRenderer 方法,他确保你能得到一个 renderer 渲染器。renderer 是通过调用创建渲染器的 createRenderer 来生成的,这个 createRenderer 来自于 runtime-core,后面我们会看到。

而这个 rendererOptions 是什么呢?

const rendererOptions = extend({ patchProp, forcePatchProp }, nodeOps);export const nodeOps: Omit<RendererOptions<Node, Element>, "patchProp"> = {insert: (child, parent, anchor) => {parent.insertBefore(child, anchor || null);},remove,createElement,createText,// ...
};

是不是就是一些 DOM API 的高阶封装,这个在 vue 的生态中,叫平台特性。vue 源码中的平台特性就是针对 web 平台的。如果开发者想要在别的平台上运行 vue,比如 mpvue、weex,就不需要 fork 源码库改源码了,直接把 nodeOps 中的方法按着平台的特性逐一实现就可以了。这也是 createRenderer 等跨平台的代码放到 runtime-core 中的原因。

当然 runtime-dom 远远不只图中这些东西,我们先大致过一下初始化过程,以对 vue3.0 有一个大致的了解。

runtime-core

紧接着,进入 runtime-core,创建渲染器

我们注意 baseCreateRenderer 这个 fn,2000 多行的代码量,里面的东西都是渲染的核心代码,从平台特性 options 取出相关 API,实现了 patch、处理节点、处理组件、更新组件、安装组件实例等等方法,最终返回了一个对象。这里我们看到了【2】中渲染器调用的 createApp 方法,他是通过 createAppAPI 创建的。代码进入 createAppAPI

这里我们又看见了熟悉的 Vue2.x 中的 API,挂载在 app 上面。

至此,Vue.createApp(app).mount("#app"),创建 app 实例的流程,终于在【7】中 return app 告一段落,我们拿到了【2】中的 app 实例。

大致瞄一眼 app ,我们可以在 apiCreateApp.ts 中找到其实现

初次渲染 .mount("#app")

上面的介绍中,其实有两处 .mount 的实现,一处是在 runtime-dom【2】中的 mount,我们叫他 dom-mount。一处是【7】中的 mount,我们叫他 core-mount

dom-mount的实现:

const { mount } = app; // 先暂存'core-mount'
app.mount = (containerOrSelector: Element | string): any => {const container = normalizeContainer(containerOrSelector); // #app dom 节点if (!container) return;const component = app._component;if (!isFunction(component) && !component.render && !component.template) {component.template = container.innerHTML; // 平台特性的逻辑}// clear content before mountingcontainer.innerHTML = "";const proxy = mount(container); // 执行'core-mount'container.removeAttribute("v-cloak");return proxy;
};

dom-mount 并不是重写 core-mount,而是提取了平台特性的逻辑。比如上面如果 component 不是 function,又没有 rendertemplate,就读取 dom 节点内部的 html 作为渲染模板。

然后再执行 core-mountmount(container)

代码很简单,就两步:

  • 创建根组件的 vnode

  • 渲染这个 vnode

创建根组件的vnode

创建 vnode,是一个初始化 vnode 的过程,这个阶段中,下面的这些属性被初始化为具体的值(还有很多属性没有罗列,都是初始值)。

vnode 描述不同的事物时,他的属性值也各不相同,这些在 vnode 初始化阶段确定的属性在渲染组件时,能带来非常重要的效率提升。

  • type,标识 VNode 的种类

  1. html 标签的描述,type 属性就是一个字符串,即标签的名字

  2. 组件的描述,type 属性就是引用组件类(或函数)本身

  3. 文本节点的描述,type 属性就是 null

  • patchFlag,标识组件变化的地方

  • shapeFlagVNode 的标识,标明 VNode 属于哪一类,demo 中的shapeFlag4STATEFUL_COMPONENT,有状态的组件。

packages/shared/src/shapeFlags.ts中,定义了这些通过将十进制数字 1 左移不同的位数得来的枚举值。

export const enum ShapeFlags {ELEMENT = 1, // 1 - html/svg 标签FUNCTIONAL_COMPONENT = 1 << 1, // 2 - 函数式组件STATEFUL_COMPONENT = 1 << 2, // 4 - 有状态组件TEXT_CHILDREN = 1 << 3, // 8ARRAY_CHILDREN = 1 << 4, // 16SLOTS_CHILDREN = 1 << 5, // 32TELEPORT = 1 << 6, // 64SUSPENSE = 1 << 7, // 128COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8, // 256 - 需要被 keepAlive 的有状态组件COMPONENT_KEPT_ALIVE = 1 << 9, // 512 - 已经被 keepAlive 的有状态组件COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT // 组件
}

为什么为 VNode 标识这些枚举值呢?在 Vue2.xpatch 过程中,代码通过 createElm 区分 VNode 是 html 还是组件或者 text 文本。

所以 Vue2.xpatch 是一个试错过程,在这个阶段是有很大的性能损耗的。Vue3.0 把对 VNode 的判断放到了创建的时候,这样在 patch 的时候就能避免消耗性能的判断。

最终,我们看一下 vnode 的结构

export interface VNode<HostNode = RendererNode,HostElement = RendererElement,ExtraProps = { [key: string]: any }
> {/*** @internal*/__v_isVNode: true // 一个始终为 true 的值,有了它,我们就可以判断一个对象是否是 VNode 对象/*** @internal 内部属性*/[ReactiveFlags.SKIP]: truetype: VNodeTypesprops: (VNodeProps & ExtraProps) | nullkey: string | number | nullref: VNodeNormalizedRef | nullscopeId: string | null // SFC onlychildren: VNodeNormalizedChildrencomponent: ComponentInternalInstance | nulldirs: DirectiveBinding[] | nulltransition: TransitionHooks<HostElement> | null// DOM 相关el: HostNode | nullanchor: HostNode | null // fragment anchortarget: HostElement | null // teleport targettargetAnchor: HostNode | null // teleport target anchorstaticCount: number // number of elements contained in a static vnode// suspense 支持 suspense 的属性suspense: SuspenseBoundary | nullssContent: VNode | nullssFallback: VNode | null// optimization only 优化模式中使用的属性shapeFlag: numberpatchFlag: numberdynamicProps: string[] | nulldynamicChildren: VNode[] | null// application root node onlyappContext: AppContext | null
}

渲染这个vnode

ok,书接上回,我们拿到 根组件的 VNode,接下来执行到 render 函数。

render 的核心逻辑就是 patch 函数。

patch 函数

patch 有两种含义: 1)整个虚拟 dom 映射到真实 dom 的过程;2)patch 函数。我们这里讲的是函数。

patch 就是 render 渲染组件的关键逻辑,【5】中 baseCreateRenderer 2000 行左右的代码,主要是为了 patch 服务的。

// patching & not same type, unmount old tree
if (n1 && !isSameVNodeType(n1, n2)) {anchor = getNextHostNode(n1)unmount(n1, parentComponent, parentSuspense, true)n1 = null
}
// 对于前后节点类型不同的,vue 是直接卸载之前的然后重新渲染新的,不会考虑可能的子节点复用。
...const { type, ref, shapeFlag } = n2
switch (type) { // 根据节点类型 type 分发到不同的 processcase Text:processText(n1, n2, container, anchor)breakcase Comment:processCommentNode(n1, n2, container, anchor)breakcase Static:...case Fragment: ...default: // 根据不同的节点标识 shapeFlag 分发到不同的 processif (shapeFlag & ShapeFlags.ELEMENT) { processElement(...) } else if (shapeFlag & ShapeFlags.COMPONENT) {processComponent(...)...

patch 根据节点 VNode(4.1 创建的根组件的 vnode) 的 typeshapeFlags 执行不同的 process

  1. type:Text 文本

  2. type:Comment 注释

  3. type:Static 静态标签

  4. type:Fragment 片段:VNode 的类型是 Fragment,就只需要把该 VNode 的子节点渲染到页面。有了他,就没有只能有一个根节点的限制,也可以做到组件平级递归

  5. shapeFlags:ShapeFlags.ELEMENT 原生节点,html/svg 标签

  6. shapeFlags:ShapeFlags.COMPONENT 组件节点

  7. shapeFlags:ShapeFlags.TELEPORT 传送节点,将组件渲染的内容传送到制定的 dom 节点中

  8. shapeFlags:ShapeFlags.SUSPENSE 挂起节点(异步渲染)

Vue3 新增组件 - Fragment、Teleport、Suspense,可见此链接 (https://www.yuque.com/hugsun/vue3/component)

我们的 demo 中的根组件 VNodeshapeFlag4(0100)ShapeFlags.COMPONENT(0110),按位与后结果为非零,代码会进入 processCompoent

processXXX

processXXX 是对挂载(mount)和更新(update)补丁的统一操作入口。

processXXX 会根据节点是否是初次渲染,进行不同的操作。

  • 如果没有老的 VNode,就挂载组件(mount)。首次挂载,递归创建真实节点。

  • 如果有老的 VNode,就更新组件(update)。更新补丁的的渲染系统的介绍放到下下篇来介绍。

挂载

创建组件内部实例

内部实例也会暴露一些实例属性给其他更高级的库或工具使用。组件实例属性很多很重要也能帮助理解,可以在 packages/runtime-core/src/component.ts 查看实例的接口声明 ComponentInternalInstance。很壮观啊,啪的一下 100 多行属性的定义,主要包括基本属性、响应式 state 相关、suspense 相关、生命周期钩子等等

安装组件实例
  1. 初始化 props 和 slots

  2. 安装有状态的组件,这里会初始化组件的响应式

【15】setupStatefulComponent,调用了 setup(props, setupContext)

如果没有 setup 时会调用 applyOptions,应用 vue2.xoptions API,最终对 data() 的响应式处理也是使用 vue3.0reactive

上面讲过,安装组件实例触发响应式初始化就发生在这里,具体怎么触发的,这块又是一个千层套路,放到下一篇中。

【16】主要是根据 template 拿到组件的 render 渲染函数和应用 vue2.xoptions API

我们看一下 template 模板编译后生成的 render 函数。

我们大致看下生成的 render 函数,有几点需要注意

  1. 这里的 render 函数执行后的返回是组件的 VNode

  2. _createVNode 函数,用于创建 VNode

  3. _createVNode函数的入参,typepatchFlagsdynamicProps

function _createVNode(type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, // type,标识 VNode 的种类props: (Data & VNodeProps) | null = null,children: unknown = null,patchFlag: number = 0, // 标记节点动态变化的地方dynamicProps: string[] | null = null, // 动态 propsisBlockNode = false
): VNode { ... }

createVNode 在创建根节点的时候就出现过,用于创建虚拟 DOM。这个是内部使用的 API,面向用户的 API 还是h函数。

export function h(type: any, propsOrChildren?: any, children?: any): VNode { ... }

h 的实现也是调用 createVNode,但是没有 patchFlagdynamicPropsisBlockNode 这三个参数。也就是 h 是没有 optimization 的,应该是因为这三个参数,让用户自己算容易出错。

看来这个 patchFlags 有点意思,标识组件变化的地方,用于 patch 的 diff 算法优化

export const enum PatchFlags {TEXT = 1, // 动态文字内容CLASS = 1 << 1, // [2]动态 class 绑定STYLE = 1 << 2, // [4]动态样式PROPS = 1 << 3, // [8]动态 props,不是 class 和 style 的动态 propsFULL_PROPS = 1 << 4, // [16]有动态的 key,也就是说 props 对象的 key 不是确定的。key 变化时,进行一次 full diffHYDRATE_EVENTS = 1 << 5, // [32]STABLE_FRAGMENT = 1 << 6, // [64]children 顺序确定的 fragmentKEYED_FRAGMENT = 1 << 7, // [128]children 中有带有 key 的节点的 fragmentUNKEYED_FRAGMENT = 1 << 8, // [256]没有 key 的 children 的 fragmentNEED_PATCH = 1 << 9, // [512]DYNAMIC_SLOTS = 1 << 10, // [1024]动态的插槽// SPECIAL FLAGS -------------------------------------------------------------// 以下是特殊的 flag,负值HOISTED = -1, // 表示他是静态节点,他的内容永远不会改变BAIL = -2, // 用来表示一个节点的 diff 应该结束
}

之所以使用位运算,是因为

  • | 来进行复合,TEXT | PROPS得到0000 1001,即十进制 9。标识他既有动态文字内容,也有动态 props。

  • & 进行 check,patchFlag & TEXT0000 1001 & 0000 0001,得到0000 0001,只要结果大于 0,就说明属性命中。

  • 方便扩展、计算更快...

patchFlag 被赋值到 VNode 的属性中,他在后面更新节点时会被用到。为了配合代码的正常流转,先放一放,代码继续 F10。如果你去调试代码,会发现这真的是千层套路啊,一直 shift + F11 跳出代码到怀疑人生,才终于回到 mountComponent...

总结一下 setupComponent 安装组件实例,主要做了什么事情:initProps、initSlots、响应式初始化、得到模板的 render 函数等等。

回顾前文,跳出到【13】,setup 安装组件实例后,下一步是 setupRenderEffect 激活渲染函数的副作用

激活渲染函数的副作用 setupRenderEffect

实现基于【21】,effect 副作用,意味着响应式数据变化后引起的变更。effect 源自 reactive,传入一个 fn 得到一个 reactiveEffect

effect 的入参 componentEffect 是一个命名函数,会立即执行。componentEffect 执行过程中,触发响应式数据的 getter 拦截,会在全局数据响应关系仓库记录当前componentEffect。在响应式对象发生改变时,派发更新,执行componentEffect

回到componentEffect

function componentEffect() {if (!instance.isMounted) {let vnodeHook: VNodeHook | null | undefinedconst { el, props } = initialVNodeconst { bm, m, parent } = instance// beforeMount hook 生命周期钩子函数if (bm) {invokeArrayFns(bm)}...// subTree 根节点的 subTree,通过 renderComponentRoot 根据 render 生成的 vnode//大家回忆一下 render 是什么?是不是根组件的 template 编译后得到的好多_createVNode 的渲染器函数?const subTree = (instance.subTree = renderComponentRoot(instance))...// 更新patch(null, subTree, container, ...)...if (m) { // parent 的 mounted 执行之前,先执行 subTree 的 patchqueuePostRenderEffect(m, parentSuspense)}...instance.isMounted = true // 标志实例已挂载} else { ... }
}

执行前面编译后得到的渲染函数 render,生成subTree: vnode

最后执行 patch,上文中渲染根节点的 vnode 时执行过 patch,这里就进入了一个大循环,根据组件的 childrentypeshapeFlagbaseCreateRenderer 会继续进行各种 processXXX 处理,直至基于 平台特性DOM 操作 挂载到各自的父节点中。

这个顺序是深度遍历的过程,子节点的 patch 完成之后再进行父节点的 mounted

patch 循环 && subTree 一览

// subTree 的 模板 template
<div id="app"><h1>composition-api</h1><p @click="add" :attr-key="counter">{{counter}}</p><p :class="{'counter': counter % 2}">{{doubleCounter}}</p>
</div>// patchFlag: 64 
// STABLE_FRAGMENT = 1 << 6, // 64 表示:children 顺序确定的 fragment
// shapeFlag: 16
// ARRAY_CHILDREN = 1 << 4, // 16 
  1. 观察上面这个模板,Vue2.x 中的模板只能有一个根元素,Vue3.0 的这个 demo 中有三个根元素,这得益于新增的 fragment 组件。

  2. vnode 标识出来 patchFlag:64,表示 children 顺序确定的 fragment

  3. vnode 标识出来 shapeFlag:16,表示当前节点是一个孩子数组。

  4. vnode 标识出来 dynamicChildren,标识动态变化的孩子节点。显然是两个 p 标签,可以想象这个数组的元素也是当前呈现的 vnode,只不过具体属性值不同罢了

等等,还有 4 吗,我不知道...

当然还有,processxxx 中一般都会判断是挂载还是更新,更新的时候就会用到 patchFlag,比如 patchElement... 下次一定

等等,还有 5 吗,我不知道...

当然还有,第五层我就已经裂开了啊...

あ:あげない      あ:不给你哦~ ????????????
い:いらない,    い:不要了啦~ ????????????
う:うごけない    う:动不了了~ ????????????
え:えらべない    え:不会选嘛~ ????????????
お:おせない      お:按不到耶~ [裂开][裂开][裂开]

刚看源码不久,只能靠 F11 、参考其他文档,凭自己的理解写出这样的文章,肯定有很多理解不对的地方,希望得到批判指正。

附录

  • Vue3初始化.drawio (https://www.yuque.com/office/yuque/0/2020/drawio/441847/1605880555730-4e18923f-c087-4082-af06-ec51986ba658.drawio?from=https%3A%2F%2Fwww.yuque.com%2Fdocs%2Fshare%2F64bd5cdc-3086-4154-a447-04032d161830%3F%23)

推荐阅读

我在阿里招前端,我该怎么帮你?(现在还可以加模拟面试群)
如何拿下阿里巴巴 P6 的前端 Offer
如何准备阿里P6/P7前端面试--项目经历准备篇
大厂面试官常问的亮点,该如何做出?
如何从初级到专家(P4-P7)打破成长瓶颈和有效突破
若川知乎问答:2年前端经验,做的项目没什么技术含量,怎么办?
若川知乎高赞:有哪些必看的 JS库?

末尾

你好,我是若川,江湖人称菜如若川,历时一年只写了一个学习源码整体架构系列~(点击蓝字了解我)

  1. 关注若川视野,回复"pdf" 领取优质前端书籍pdf,回复"1",可加群长期交流学习

  2. 我的博客地址:https://lxchuan12.gitee.io 欢迎收藏

  3. 觉得文章不错,可以点个在看呀^_^另外欢迎留言交流~

小提醒:若川视野公众号面试、源码等文章合集在菜单栏中间【源码精选】按钮,欢迎点击阅读,也可以星标我的公众号,便于查找

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/276351.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2020年大前端技术趋势解读

导Lead语如今的前端早已不再拘泥于满足页面展示&#xff0c;而是开始延展到通过全栈来闭环产品。这表明前端已经有能力透过业务深入产业&#xff0c;继而影响商业结果。这种表象的改变背后是本质的转变&#xff0c;从更为宏观的角度来说&#xff0c;前端正在通过持续的技术革新…

lc滤波器是利用电感的感抗_你对LC谐振电路你都了解吗

根据在电路中电感器L和电容C的连接方式不同&#xff0c;可以有两种LC谐振电路&#xff0c;LC并联谐振电路和LC串联谐振电路。LC并联、串联谐振电路在应用中的变化较多&#xff0c;是电路中分析的一个难点&#xff0c;只有掌握LC并联、串联电路的阻抗特性等基本概念&#xff0c;…

给小程序再减重 30% 的秘密​(京喜小程序首页瘦身实践)

前言—在 web 开发场景&#xff0c;减少代码体积虽然是性能优化的一个方向&#xff0c;还没到锱铢必较的程度。但是在小程序场景&#xff0c;由于代码包上传阶段限制了主包 2M 和总包 16M&#xff08;近期微信官方正在内测将总包上限调整至 20M &#xff09;的尺寸&#xff0c;…

本周ASP.NET英文技术文章推荐[10/21 – 10/27]

这一篇是《本周ASP.NET英文技术文章推荐》系列的第一篇&#xff0c;在这个系列中&#xff0c;我将介绍5-10篇比较有价值的、本周发布的、与ASP.NET相关的英文技术文章&#xff0c;帮助各位朋友从良莠不齐的大量文章中挑出一些我认为非常有价值阅读的&#xff0c;在进行一段简要…

3 年前端面经和他在创业公司的成长历程

在掘金上当了几年的伸手党&#xff0c;最近也准备输出一些自己的东西。关于我首先介绍一下我自己&#xff0c;17 年毕业于一所 211 学校&#xff0c;但是由于大学四年驰骋在召唤师峡谷&#xff0c;毕业时也没有找到一份大厂的工作&#xff0c;随便找了一家创业公司签了三方就去…

Spring.NET学习笔记9——打造简易的依赖注入框架(练习篇) Level 100

我们在第三篇中学习里一个简易的IoC框架。今天我们接着上次的程序&#xff0c;实现带参数构造函数对象的实例和属性的注入 。  我们知道可以通过反射获取类的构造函数及参数(GetConstructors方法)&#xff1b;可以获取属性和属性的类型(GetProperties方法)。通过Activator的C…

android 单元测试

首先AndroidManifest.xml View Code <?xml version"1.0" encoding"utf-8"?> <manifest xmlns:android"http://schemas.android.com/apk/res/android"package"com.travelsky.test" android:versionCode"1"androi…

申万一级行业日指数_基金收评 | 指数震荡走弱,军工股成两市主线!后期行情如何?...

收评君复盘日记(2020年9月21日)三大指数集体收跌&#xff0c;北向资金全天大幅净流出近65亿元&#xff0c;军工板块表现强势。盘面回顾9月21日&#xff0c;两市全天高开低走&#xff0c;早盘指数弱势震荡&#xff0c;三大指数盘中一度翻红&#xff0c;但随后震荡走弱&#xff0…

若川的2016年度总结,毕业工作

可以点击上方的标签若川的故事、年度总结&#xff0c;查看往期文章有读者反馈说看我年度总结系列比我源码系列更有启发。所以打算把2016-2018的年度总结发布到公众号声明原创&#xff0c;希望对大家有所启发。&#xff08;虽然我的每一年都过得非常普通...&#xff09;以下是正…

jQuery之Ajax

转载链接&#xff1a;http://cargoj.iteye.com/blog/1008047 1 . jQuery帮助之Ajax请求&#xff08;一&#xff09;jQuery.ajax(options) 2 . jQuery帮助之Ajax请求&#xff08;二&#xff09;jQuery.get(url,[data],[callback] 3 . jQuery帮助之Ajax请求&#xff08;三&am…

面试官问:能否模拟实现JS的new操作符(高频考点)

可以点击上方的话题JS基础系列&#xff0c;查看往期文章这篇文章写于2018年11月05日&#xff0c;new模拟实现&#xff0c;Object.create是面试高频考点&#xff0c;之前发布在掘金有近2万人阅读&#xff0c;现在发布到公众号声明原创。1. 前言这是面试官问系列的第一篇&#xf…

跟我一起学WCF(2)——利用.NET Remoting技术开发分布式应用

一、引言 上一篇博文分享了消息队列&#xff08;MSMQ&#xff09;技术来实现分布式应用&#xff0c;在这篇博文继续分享下.NET平台下另一种分布式技术——.NET Remoting。 二、.NET Remoting 介绍 2.1 .NET Remoting简介 .NET REmoting与MSMQ不同&#xff0c;它不支持离线可得&…

二叉树的建立与遍历_51、二叉树遍历-重建二叉树JZ4

题目描述输入某二叉树的前序遍历和中序遍历的结果&#xff0c;请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}&#xff0c;则重建二叉树并返回。思路回顾三种经典的遍历&…

推荐关注这7个高质量的前端公众号

拓宽眼界&#xff0c;增加深度&#xff0c;在阅读的世界里&#xff0c;我们往往能找到不一样的态度&#xff0c;提升朋友圈质量&#xff0c;从关注这几个公众号开始。轻扫一下二维码就行了&#xff0c;你可以试试&#xff0c;肯定会有意外收获。大迁世界 简介&#xff1a;前端小…

Windows Live Writer 在win2003 的安装方法

下载Windows Live Writer整体安装包&#xff0c;最好是离线安装包 2.在xp系统上安装 3.查找C:\Program Files\Common Files\Windows Live\.cache目录 .cache目录是隐藏的&#xff0c;目录下面就是各个安装文件的msi安装包 4.拷贝相应的msi文件&#xff0c;到Windows 2003安装就…

decode 大于比较 小于_6 燃气输配系统6.3 压力不大于1.6Mpa的室外燃气管道城镇燃气设计规范 GB500282006(2020修订版)...

6.3 压力不大于1.6Mpa的室外燃气管道6.3.1中压和低压燃气管道宜采用聚乙烯管、机械接口球墨铸铁管、钢管或钢骨架聚乙烯塑料复合管&#xff0c;并应符合下列要求&#xff1a; 1 聚乙烯燃气管应符合现行的国家标准《燃气用埋地聚乙烯管材》GB15558.1 和《燃气用埋地聚乙烯管件…

若川的2017年度总结,一如既往

可以点击上方的标签若川的故事、年度总结&#xff0c;查看往期文章有读者反馈说看我年度总结系列比我源码系列更有启发。所以打算把2016-2018的年度总结发布到公众号声明原创&#xff0c;希望对大家有所启发。&#xff08;虽然我的每一年都过得非常普通...&#xff09;若川的20…

沟通:用故事产生共鸣

《沟通:用故事产生共鸣》(全彩) 基本信息作者&#xff1a; Nancy Duarte(南希.杜瓦特)译者&#xff1a; 冯海洋出版社&#xff1a;电子工业出版社ISBN&#xff1a;9787121195914上架时间&#xff1a;2013-4-1出版日期&#xff1a;2013 年3月开本&#xff1a;12开页码&#xff1…

若川的2018年度总结,平淡无奇

可以点击上方的标签若川的故事、年度总结&#xff0c;查看往期文章偷偷告诉你&#xff0c;公众号内回复【报告】&#xff0c;可以获取你自己的github 2020 年度报告昨晚在我的6个微信群里都发了红包&#xff0c;以这样的方式跨过了2020年。运营公众号真的挺难的&#xff0c;比如…

基于dnn的车牌识别_自然场景中文文字识别,身份证火车票都能识别

图像处理中OCR(Optical Character Recognition光学字符识别)场景非常多&#xff0c;也给大家的工作生活带来了很多便利&#xff0c;比如车牌识别就能管理停车场车辆的出入&#xff0c;快递时只需给一个带有快递信息的图就能自动解析上传发件信息和收件信息&#xff0c;再比如我…