前言

各位道友大家好,我是LSF,在上一篇博文 中,分析了Vue初始化的整体流程,最后到了 update 动态创建 DOM 阶段。接下来这篇博文,会对这个流程进行分析,重点需要掌握 createElm 函数的执行逻辑。

一、_update 如何判断是初始化还是更新操作?

_update 是在Vue实例化之前,通过prototype混入的一个实例方法。主要目的是将vnode转化成真实DOM,它定义在 core/instance/lifecycle.js 文件中。

  1. Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
  2. const vm: Component = this // vm -> this
  3. const prevEl = vm.$el
  4. // 保存上一个vnode。
  5. const prevVnode = vm._vnode
  6. // 设置 activeInstance 当前活动的vm,返回方法。
  7. const restoreActiveInstance = setActiveInstance(vm)
  8. vm._vnode = vnode // 赋值 _vnode 属性为新传入的 vnode。
  9. // Vue.prototype.__patch__ is injected in entry points
  10. // based on the rendering backend used.
  11. if (!prevVnode) {
  12. // initial render 初始化渲染,如果有子组件,会递归初始化
  13. vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
  14. } else {
  15. // updates 更新
  16. vm.$el = vm.__patch__(prevVnode, vnode)
  17. }
  18. // activeInstance 恢复到当前的vm
  19. restoreActiveInstance()
  20. // update __vue__ reference
  21. if (prevEl) {
  22. prevEl.__vue__ = null
  23. }
  24. if (vm.$el) {
  25. vm.$el.__vue__ = vm
  26. }
  27. // if parent is an HOC, update its $el as well
  28. if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
  29. vm.$parent.$el = vm.$el
  30. }
  31. // updated hook is called by the scheduler to ensure that children are
  32. // updated in a parent's updated hook.
  33. }

代码中可以看到,通过 prevVnode 是否为 null 来判断的是否是初始化 patch。由于是初始化操作,开始的时候 vm._vnode 没有被赋值成 vnode,从而 vm._vnode 为 null。所以代码的执行逻辑会走到初始化 patch。

二、patch

2.1 patch 定义

web端的 Vue.prototype.__patch__ 方法,它定义的入口在 src/platforms/web/runtime/index.js 文件中。

  1. import { patch } from './patch'
  2. ...
  3. // install platform runtime directives & components
  4. extend(Vue.options.directives, platformDirectives)
  5. extend(Vue.options.components, platformComponents)
  6. // install platform patch function
  7. // 安装web端的 patch 方法。
  8. Vue.prototype.__patch__ = inBrowser ? patch : noop
  9. ...

如果是浏览器环境下,被赋值为 patch 方法,该方法定义在 src/platforms/web/runtime/patch.js中。如果是非浏览器环境,patch 被赋值成一个空函数。

  1. /* @flow */
  2. import * as nodeOps from 'web/runtime/node-ops'
  3. import { createPatchFunction } from 'core/vdom/patch'
  4. import baseModules from 'core/vdom/modules/index'
  5. import platformModules from 'web/runtime/modules/index'
  6. // the directive module should be applied last, after all
  7. // built-in modules have been applied.
  8. const modules = platformModules.concat(baseModules)
  9. // 调用 createPatchFunction 函数,返回 patch。
  10. export const patch: Function = createPatchFunction({ nodeOps, modules })

通过代码可以看到,最终 vue 是调用了 createPatchFunction 函数,它定义在 src/core/vdom/patch.js 中。createPatchFunction 函数内部定义了如 emptyNodeAt、removeNode、createElement、createChildren 等一系列的辅助函数,通过这些辅助函数,完成了对 patch 函数的代码逻辑的封装。

2.2 初始化的 patch

创建Vue实例,或者组件实例的,patch 都会被执行。

  • 如果是创建vue实例执行 patch

    • isRealElement:判断是否是真实的DOM节点。

    • patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly):负责DOM的更新。

    • oldVnode = emptyNodeAt(oldVnode):对容器DOM进行vnode的转化。

    • createElm():创建新节点,初始化创建需要重点关注的函数。

  • 如果是创建组件实例执行的 patch

    • isInitialPatch:用户判断子组件否初次执行 patch,进行创建。

    • insertedVnodeQueue:新创建子组件节点,组件 vnode 会被push到这个队列中。

    • invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)。

具体代码注释如下

  1. export function createPatchFunction (backend) {
  2. ...
  3. return function patch (oldVnode, vnode, hydrating, removeOnly) {
  4. // 如果新的 vnode 为空,调用 destory 钩子,销毁oldVnode
  5. if (isUndef(vnode)) {
  6. if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
  7. return
  8. }
  9. // 用户判断子组件否初次执行 patch,进行创建。
  10. let isInitialPatch = false
  11. // 新创建子组件节点,组件 vnode 会被push到这个队列中
  12. const insertedVnodeQueue = []
  13. if (isUndef(oldVnode)) {
  14. // empty mount (likely as component), create new root element
  15. // 空挂载(可能作为组件),创建新的根元素
  16. isInitialPatch = true
  17. // 创建组件节点的子元素
  18. createElm(vnode, insertedVnodeQueue)
  19. } else {
  20. // 1.作为判断是否是真实的DOM节点条件
  21. const isRealElement = isDef(oldVnode.nodeType)
  22. if (!isRealElement && sameVnode(oldVnode, vnode)) {
  23. // patch existing root node
  24. // 更新操作
  25. patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
  26. } else {
  27. if (isRealElement) {
  28. ...
  29. // either not server-rendered, or hydration failed.
  30. // create an empty node and replace it
  31. // 2. 传入的容器DOM(如 el: "#app"),会在这里被转化成 vnode。
  32. oldVnode = emptyNodeAt(oldVnode)
  33. }
  34. // replacing existing element
  35. const oldElm = oldVnode.elm
  36. const parentElm = nodeOps.parentNode(oldElm)
  37. // create new node
  38. // 3. 创建新节点
  39. createElm(
  40. vnode,
  41. insertedVnodeQueue,
  42. // extremely rare edge case: do not insert if old element is in a
  43. // leaving transition. Only happens when combining transition +
  44. // keep-alive + HOCs. (#4590)
  45. oldElm._leaveCb ? null : parentElm,
  46. nodeOps.nextSibling(oldElm)
  47. )
  48. // update parent placeholder node element, recursively
  49. // 递归的更新父占位节点元素。
  50. if (isDef(vnode.parent)) {...
  51. }
  52. // destroy old node
  53. // 销毁旧节点
  54. if (isDef(parentElm)) {
  55. removeVnodes([oldVnode], 0, 0)
  56. } else if (isDef(oldVnode.tag)) {
  57. invokeDestroyHook(oldVnode)
  58. }
  59. }
  60. }
  61. // 调用 insertedVnodeQueue 队列中所有子组件的 insert 钩子。
  62. invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
  63. return vnode.elm
  64. }

三、createElm 动态创建DOM

createElm 函数是动态创建 DOM 的核心,作用是通过 vnode 创建真实的 DOM,并插入到它的父 DOM 节点中。它定义在 src/core/vdom/patch.js

的 createPatchFunction 方法中。createElm 内部创建 DOM 的主要判断逻辑,可以概括为下面几种情况。

1、如果创建组件节点

  • 如果碰到子组件标签,走创建组件节点逻辑。

  • 创建完成,插入到父亲元素中。

2、如果创建标签元素节点

  • 如果 vnode.tag 不为空,先创建标签元素, 赋值 vnode.elm 进行占位。

  • 调用 createChildren 创建子节点,最终这些子节点会 append 到 vnode.elm 标签元素中。

  • 将 vnode.elm 标签元素插入到父亲元素中。

3、如果创建注释节点

  • 如果 vnode.isComment 不为空,创建注释节点,赋值 vnode.elm。

  • 将注释节点插入到父亲元素中。

4、如果创建文本节点

  • 上面三种情况都不是,则创建文本节点,赋值 vnode.elm。

  • 将文本节点插入到父亲元素中。

  1. function createElm (
  2. vnode,
  3. insertedVnodeQueue,
  4. parentElm,
  5. refElm,
  6. nested,
  7. ownerArray,
  8. index
  9. ) {
  10. if (isDef(vnode.elm) && isDef(ownerArray)) {
  11. // This vnode was used in a previous render!
  12. // now it's used as a new node, overwriting its elm would cause
  13. // potential patch errors down the road when it's used as an insertion
  14. // reference node. Instead, we clone the node on-demand before creating
  15. // associated DOM element for it.
  16. vnode = ownerArray[index] = cloneVNode(vnode)
  17. }
  18. vnode.isRootInsert = !nested // for transition enter check
  19. // 1、如果碰到子组件标签,走创建组件节点逻辑,插入父亲节点。
  20. if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
  21. return
  22. }
  23. const data = vnode.data
  24. const children = vnode.children
  25. const tag = vnode.tag
  26. // 2、如果是标签标记,先创建标签元素进行占位。
  27. // 调用 createChildren 创建子节点(递归调用createElm)。
  28. // 将标签元素,插入父亲元素。
  29. if (isDef(tag)) {
  30. if (process.env.NODE_ENV !== 'production') {
  31. if (data && data.pre) {
  32. creatingElmInVPre++
  33. }
  34. if (isUnknownElement(vnode, creatingElmInVPre)) {
  35. warn(
  36. 'Unknown custom element: <' + tag + '> - did you ' +
  37. 'register the component correctly? For recursive components, ' +
  38. 'make sure to provide the "name" option.',
  39. vnode.context
  40. )
  41. }
  42. }
  43. // 通过上面的tag,创建标签元素,赋值给 vnode.elm 进行占位
  44. vnode.elm = vnode.ns
  45. ? nodeOps.createElementNS(vnode.ns, tag)
  46. : nodeOps.createElement(tag, vnode)
  47. setScope(vnode)
  48. /* istanbul ignore if */
  49. if (__WEEX__) {
  50. // in Weex, the default insertion order is parent-first.
  51. // List items can be optimized to use children-first insertion
  52. // with append="tree".
  53. const appendAsTree = isDef(data) && isTrue(data.appendAsTree)
  54. if (!appendAsTree) {
  55. if (isDef(data)) {
  56. invokeCreateHooks(vnode, insertedVnodeQueue)
  57. }
  58. insert(parentElm, vnode.elm, refElm)
  59. }
  60. createChildren(vnode, children, insertedVnodeQueue)
  61. if (appendAsTree) {
  62. if (isDef(data)) {
  63. invokeCreateHooks(vnode, insertedVnodeQueue)
  64. }
  65. insert(parentElm, vnode.elm, refElm)
  66. }
  67. } else {
  68. // 创建子节点
  69. createChildren(vnode, children, insertedVnodeQueue)
  70. if (isDef(data)) {
  71. invokeCreateHooks(vnode, insertedVnodeQueue)
  72. }
  73. // 将创建的标签元素节点,插入父亲元素
  74. insert(parentElm, vnode.elm, refElm)
  75. }
  76. if (process.env.NODE_ENV !== 'production' && data && data.pre) {
  77. creatingElmInVPre--
  78. }
  79. } else if (isTrue(vnode.isComment)) {
  80. // 3、创建注释节点,插入到父亲元素
  81. vnode.elm = nodeOps.createComment(vnode.text)
  82. insert(parentElm, vnode.elm, refElm)
  83. } else {
  84. // 4、创建文本节点,插入到父亲元素
  85. vnode.elm = nodeOps.createTextNode(vnode.text)
  86. insert(parentElm, vnode.elm, refElm)
  87. }
  88. }

下面对动态创建的几种情况分别进行说明。

3.1 创建组件节点

创建组件节点和 vue 的组件系统息息相关,这里先不具体展开,之后的博文中单独分析 vue 组件系统。只需要记住 vue 模板里的子组件初始化创建,是在这一步进行即可。

  1. function createElm (
  2. vnode,
  3. insertedVnodeQueue,
  4. parentElm,
  5. refElm,
  6. nested,
  7. ownerArray,
  8. index
  9. ) {
  10. ...
  11. // 创建组件节点
  12. if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
  13. return
  14. }
  15. ...
  16. }

createComponent 这个方法也定义在 src/core/vdom/patch.js 的 createPatchFunction 的方法中,这里先简单的介绍一下这个方法的内部逻辑。

  • 通过 vnode.data 中是否包含组件相关的 hook,来判断当前 vnode 是否是子组件 vnode(组件的 vnode,会包含 init 等钩子方法)。

  • 调用 init,执行子组件的初始化流程,创建子组件实例,进行子组件挂载。

  • 将生成的子组件 DOM 赋值给 vnode.elm。

  • 通过 vnode.elm 将创建的子组件节点,插入到父亲元素中。

  1. function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  2. let i = vnode.data
  3. if (isDef(i)) {
  4. const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
  5. // 调用组件 init 钩子后,会执行子组件的**初始化流程**
  6. // 创建子组件实例,进行子组件挂载。
  7. if (isDef(i = i.hook) && isDef(i = i.init)) {
  8. i(vnode, false /* hydrating */)
  9. }
  10. // after calling the init hook, if the vnode is a child component
  11. // it should've created a child instance and mounted it. the child
  12. // component also has set the placeholder vnode's elm.
  13. // in that case we can just return the element and be done.
  14. // 如果是组件实例,将创建 vnode.elm 占位符
  15. // 将生成的组件节点,插入到父亲元素中
  16. if (isDef(vnode.componentInstance)) {
  17. initComponent(vnode, insertedVnodeQueue)
  18. insert(parentElm, vnode.elm, refElm)
  19. if (isTrue(isReactivated)) {
  20. reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
  21. }
  22. return true
  23. }
  24. }
  25. }
  • 如果是创建组件节点,并且成功,createComponent 函数返回 true。createElm 函数执行到 return。

  • 如果是其他类型的节点,createComponent 函数返回 undefined,createElm 函数,会向下执行创建其他类型节点(标签元素、注释、文本)的代码逻辑。

综上所述,createElm 函数执行,只要碰到组件标签,会递归的去初始化创建子组件,简图如下所示(绿色线路部分)。

再调用 insert(parentElm, vnode.elm, refElm),将生成的组件节点插入到父亲元素中(遵从先子后父)。

3.2 创建标签元素节点

createElm 判断如果 vnode 不是组件的 vnode,它会判断是否是标签元素,从而进行创建标签元素节点的代码逻辑, 主要逻辑分析如下。

  • vnode.tag 标签属性存在,通过 tag 创建对应的标签元素,赋值给 vnode.elm 进行占位。

  • 调用 createChildren 创建子节点(遍历子vnode,递归调用 createElm 函数)。

  • 将创建的标签元素节点,插入父亲元素。

  1. function createElm (
  2. vnode,
  3. insertedVnodeQueue,
  4. parentElm,
  5. refElm,
  6. nested,
  7. ownerArray,
  8. index
  9. ) {
  10. ...
  11. const data = vnode.data
  12. const children = vnode.children
  13. const tag = vnode.tag
  14. // 2、如果是标签标记,先创建标签元素进行占位。
  15. // 调用 createChildren 创建子节点(遍历子vnode,递归调用 createElm 函数)。
  16. // 将标签元素,插入父亲元素。
  17. // 如果标签属性不为空
  18. if (isDef(tag)) {
  19. if (process.env.NODE_ENV !== 'production') {
  20. if (data && data.pre) {
  21. creatingElmInVPre++
  22. }
  23. // 不合法的标签进行提示
  24. if (isUnknownElement(vnode, creatingElmInVPre)) {
  25. warn(
  26. 'Unknown custom element: <' + tag + '> - did you ' +
  27. 'register the component correctly? For recursive components, ' +
  28. 'make sure to provide the "name" option.',
  29. vnode.context
  30. )
  31. }
  32. }
  33. // 通过上面的tag,创建标签元素,赋值给 vnode.elm 进行占位
  34. vnode.elm = vnode.ns
  35. ? nodeOps.createElementNS(vnode.ns, tag)
  36. : nodeOps.createElement(tag, vnode)
  37. setScope(vnode)
  38. /* istanbul ignore if */
  39. if (__WEEX__) {
  40. ...
  41. } else {
  42. // 创建子节点
  43. createChildren(vnode, children, insertedVnodeQueue)
  44. if (isDef(data)) {
  45. // vnode.data 不为空,调用所有create的钩子。
  46. invokeCreateHooks(vnode, insertedVnodeQueue)
  47. }
  48. // 将创建的标签元素节点,插入父亲元素
  49. insert(parentElm, vnode.elm, refElm)
  50. }
  51. if (process.env.NODE_ENV !== 'production' && data && data.pre) {
  52. creatingElmInVPre--
  53. }
  54. ...
  55. }

createChildren 函数主要逻辑如下

  • 如果 vnode.children 是子 vnode 数组,遍历 vnode.children 中的每个子 vnode,递归的调用了 createElm 函数,创建对应的子节点,并插入到父亲元素中(此时的父亲元素 parentElm 为 vnode.elm)。

  • 如果 vnode.text 为空字符串。就创建一个空文本节点,插入到 vnode.elm 元素中。

  1. function createChildren (vnode, children, insertedVnodeQueue) {
  2. if (Array.isArray(children)) {
  3. if (process.env.NODE_ENV !== 'production') {
  4. checkDuplicateKeys(children)
  5. }
  6. // 遍历子vnode数组,递归调用 createElm
  7. for (let i = 0; i < children.length; ++i) {
  8. createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
  9. }
  10. } else if (isPrimitive(vnode.text)) {
  11. // 创建空文本节点,appendChildren 到 vnode.elm 中
  12. nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
  13. }
  14. }

上面已经创建完成子标签节点,invokeCreateHooks 调用执行所有子组件相关的 create 钩子。这个方法createElm、

initComponent 中都会被调用。如果在 initComponent 中调用,说明创建的子节点中有组件节点,还会将组件 vnode 添加到 insertedVnodeQueue 队列中。

  1. // createElm 中
  2. if (isDef(data)) {
  3. // vnode.data 不为空,调用所有create的钩子。
  4. invokeCreateHooks(vnode, insertedVnodeQueue)
  5. }
  1. function initComponent (vnode, insertedVnodeQueue) {
  2. if (isDef(vnode.data.pendingInsert)) {
  3. insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert)
  4. vnode.data.pendingInsert = null
  5. }
  6. vnode.elm = vnode.componentInstance.$el
  7. if (isPatchable(vnode)) {
  8. invokeCreateHooks(vnode, insertedVnodeQueue)
  9. setScope(vnode)
  10. } else {
  11. // empty component root.
  12. // skip all element-related modules except for ref (#3455)
  13. registerRef(vnode)
  14. // make sure to invoke the insert hook
  15. insertedVnodeQueue.push(vnode)
  16. }
  17. }
  1. function invokeCreateHooks (vnode, insertedVnodeQueue) {
  2. // 所有组件相关的create钩子都调用
  3. // initComponent调用的话,还会将各个子组件的 vnode 添加到 insertedVnodeQueue 队列中。
  4. for (let i = 0; i < cbs.create.length; ++i) {
  5. cbs.create[i](emptyNode, vnode)
  6. }
  7. i = vnode.data.hook // Reuse variable
  8. if (isDef(i)) {
  9. if (isDef(i.create)) i.create(emptyNode, vnode)
  10. if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
  11. }
  12. }

综上所述,createElm 创建标签节点内部通过 createChildren 实现了对 createElm 的遍历递归调用,实现了深度优先遍历,简图如下所示(蓝色线路部分)。

再调用 insert(parentElm, vnode.elm, refElm),将生成的元素节点插入到父亲元素中(遵从先子后父)。

3.3 创建注释节点

如果不是创建组件节点和元素节点,vue 就通过 vnode.isComment 属性判断,是否创建注释节点。创建完成之后,插入到父亲元素中(遵从先子后父)

  1. vnode.elm = nodeOps.createComment(vnode.text)
  2. insert(parentElm, vnode.elm, refElm)

3.3 创建文本节点

如果不是创建组件节点、元素节点、注释节点,vue 就创建文本节点,创建完成之后,插入到父亲元素中(遵从先子后父)。

  1. vnode.elm = nodeOps.createTextNode(vnode.text)
  2. insert(parentElm, vnode.elm, refElm)

四、销毁旧节点

通过前面章节的分析,知道了 patch 函数,主要通过 createElm 动态的创建好了 DOM,并且已经成功添加到了旧DOM的后面,所以下一步操作,就只需要将旧 DOM 进行删除即可。

  1. // destroy old node
  2. // 销毁旧的节点(如 el: "app" 这个DOM)
  3. // 创建完成的整个dom会append到 el: "app", 的父亲元素(如 parentElm 为 body)上
  4. if (isDef(parentElm)) {
  5. removeVnodes([oldVnode], 0, 0)
  6. } else if (isDef(oldVnode.tag)) {
  7. invokeDestroyHook(oldVnode)
  8. }

五、总结

  • vue 通过调用 patch 函数进行初始化 DOM 的创建。

  • patch 的关键是理解内部 createElm 这个函数,它会判断组件、元素、注释、文本这些类型的节点,来创建相应的DOM,完成之后添加到父元素。

  • vue 的组件系统实现,关键在于动态创建组件节点的逻辑当中。

  • 新 DOM 创建添加过程是从子到父的,而组件的实例化是从父到子的。

Vue.js源码解析-Vue初始化流程之动态创建DOM的更多相关文章

  1. Vue.js源码解析-Vue初始化流程

    目录 前言 1. 初始化流程概述图.代码流程图 1.1 初始化流程概述 1.2 初始化代码执行流程图 2. 初始化相关代码分析 2.1 initGlobalAPI(Vue) 初始化Vue的全局静态AP ...

  2. Vue.js源码解析-从scripts脚本看vue构建

    目录 1. scripts 脚本构建 1.1 dev 开发环境构建过程 1.1.1 配置文件代码 1.1.2 如何进行代码调试? 1.2 build 生产环境构建过程 1.2.1 scripts/bu ...

  3. 从Vue.js源码角度再看数据绑定

    写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出.文章的原地址:https://github.com/an ...

  4. Vue.js 源码分析(一) 代码结构

    关于Vue vue是一个兴起的前端js库,是一个精简的MVVM.MVVM模式是由经典的软件架构MVC衍生来的,当View(视图层)变化时,会自动更新到ViewModel(视图模型),反之亦然,View ...

  5. 从template到DOM(Vue.js源码角度看内部运行机制)

    写在前面 这篇文章算是对最近写的一系列Vue.js源码的文章(https://github.com/answershuto/learnVue)的总结吧,在阅读源码的过程中也确实受益匪浅,希望自己的这些 ...

  6. Vue.js 源码构建(三)

    Vue.js 源码是基于 Rollup 构建的,它的构建相关配置都在 scripts 目录下. 构建脚本 通常一个基于 NPM 托管的项目都会有一个 package.json 文件,它是对项目的描述文 ...

  7. vue.js源码精析

    MVVM大比拼之vue.js源码精析 VUE 源码分析 简介 Vue 是 MVVM 框架中的新贵,如果我没记错的话作者应该毕业不久,现在在google.vue 如作者自己所说,在api设计上受到了很多 ...

  8. Vue.js源码——事件机制

    写在前面 因为对Vue.js很感兴趣,而且平时工作的技术栈也是Vue.js,这几个月花了些时间研究学习了一下Vue.js源码,并做了总结与输出.文章的原地址:https://github.com/an ...

  9. 2018-11-23 手工翻译Vue.js源码:尝试重命名标识符与文本

    续前文: 手工翻译Vue.js源码第一步:14个文件重命名 对core/instance/索引中的变量, 方法进行重命名如下(题图): import { 混入初始化 } from './初始化' im ...

随机推荐

  1. MinkowskiNonlinearities非线性

    MinkowskiNonlinearities非线性 MinkowskiReLU class MinkowskiEngine.MinkowskiReLU(*args, **kwargs) __init ...

  2. jvm调优神器——arthas

    在上一篇<jvm调优的几种场景>中介绍了几种常见的jvm方面调优的场景,用的都是jdk自带的小工具,比如jps.jmap.jstack等.用这些自带的工具排查问题时最大的痛点就是过程比较麻 ...

  3. 一文彻底理解Apache Hudi的多版本清理服务

    Apache Hudi提供了MVCC并发模型,保证写入端和读取端之间快照级别隔离.在本篇博客中我们将介绍如何配置来管理多个文件版本,此外还将讨论用户可使用的清理机制,以了解如何维护所需数量的旧文件版本 ...

  4. 【NX二次开发】Block UI 切换开关

    属性说明 常规         类型 描述     BlockID     String 控件ID     Enable     Logical 是否可操作     Group     Logical ...

  5. 裸辞闭关2个月,成功进大厂!吃透这份562页《算法知识手册》,化身offer收割机!

    前言 记得我上本科的时候,我们老师一直跟我们强调:"算法才是编程的灵魂,一定要把算法学好."因为不管你是Java编程爱好者.还是python的忠实粉丝,亦或觉得PHP才是这个世界最 ...

  6. vscode 配置 Pug Compile Hero Pro 插件步骤

    这个随笔主要介绍 vscode 配置 Pug Compile Hero Pro 插件的步骤,实现快速使用less 以及 scss 等的编程语言 第一步 当然是安装我们的插件啦! 在插件商店里 搜 Sa ...

  7. python用random模块模拟抽奖逻辑(print修改end参数使打印结果不分行)

    import random   #引入random模块,运用random函数list_one=["10081","10082","10083" ...

  8. 什么IP欺骗?

    1.什么是IP欺骗? IP欺骗是指创建源地址经过修改的Internet协议(IP) 数据包,目的要么是隐藏发送方的身份,要么是冒充其他计算机系统,或者两者兼具.恶意用户往往采用这项技术对目标设备或周边 ...

  9. 一QT获取当前时间和日期

    获取日期和时间使用QDateTime类,该类中有一个静态成员函数可以返回当前的时间信息 我们可以直接调用这个静态函数获取当前时间 QDateTime time = QDateTime::current ...

  10. bootstrap validate 验证插件 动态添加和动态删除验证项

    //添加验证项 function addField(field, notEmptyMsg, othercon) { if (!othercon) { $("#gyssave").b ...