Vue中Vnode的创建与处理

Vue中Vnode的创建与处理什么是虚拟 DOM 虚拟 DOM VirtualDOM 是使用 JavaScript 对象描述真实 DOMVue js 中的虚拟 DOM 借鉴 Snabbdom 并添加了 Vue js 的特征 例如 指令和组件机制为什么要使用虚拟 DOM 避免直接操作真实 DOM 提高开发效率作为一个中间层可以跨平台 支持 web 端渲染 还可以支持服务端渲染虚拟 DOM 不一定可以提高性能 首次渲染的时候会增加开销复杂视图情况下提升渲染性能 h 函数 vm createElemen atg data children

文章比较长,首先先看一下vnode的创建和处理过程的简要(源码下面都有注释,需要可以细看):
在这里插入图片描述

什么是虚拟DOM

虚拟DOMVirtual DOM)是使用JavaScript对象描述真实DOM
Vue.js中的虚拟DOM借鉴Snabbdom,并添加了Vue.js的特征,例如:指令和组件机制

为什么要使用虚拟DOM:

  • 避免直接操作真实DOM,提高开发效率
  • 作为一个中间层可以跨平台,支持web端渲染,还可以支持服务端渲染
  • 虚拟DOM不一定可以提高性能:
    • 首次渲染的时候会增加开销
    • 复杂视图情况下提升渲染性能

h函数

  • vm.$createElement(tag, data, children, normalizeChildren)
  • tag:标签名或者组件对象
  • data:描述tag,可以设置DOM的属性或者标签的属性
  • childrentag中的文本内容或者子节点
render(h){ 
    // h(tag, data, children) // return h('h1', this.msg) // return h('h1', {domProps: {innerHTML: this.msg}}) // return h('h1', {attrs: {id: 'title'}}, this.msg) // children 当前h1对应的子节点 // data 创建函数时传递的data选项 // elm 真实dom // tag 调用h函数时传递的第一个属性 const vnode = h( 'h1', { 
    attrs: { 
    id:'title' } }, this.msg ) console.log(vnode); return vnode; } 
  • vNode核心属性:tagdatachildrentextelmkey

Vnode的创建过程

1)Vnode的创建是在updateComponent函数中

  • 通过vm._render()生成虚拟dom,并通过vm._update函数将虚拟dom转为真实dom渲染至页面中;(updateComponent函数在前面文章讲watcher时有提到过,是在创建watcher实例时完成调用的;)
     // src\core\instance\lifecycle.js updateComponent = () => { 
          // vm._render() 生成虚拟DOM // vm._update用来将虚拟DOM转换为真实DOM,再渲染到页面中 vm._update(vm._render(), hydrating) } 

2)vue实例的_render方法(render方法调用)(src\core\instance\render.js)

  • 会获取render函数(可以是用户传递的render函数或者Vue编译生成的render函数);
  • 并调用vnode = render.call(vm._renderProxy, vm.$createElement)来生成vnode

3)$createElement((src\core\instance\render.js))

  • 在调用render函数时,会传递一个h参数,这个h参数就是$createElement,而$createElement最终调用的是createElement方法
     // 对编译生成的render进行渲染的方法 vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) // normalization is always applied for the public version, used in // user-written render functions. // 对手写render函数进行渲染的方法 vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true) 

4)createElement(参数处理)(src\core\vdom\create-element.js)

  • createElement中,主要是对参数进行了一下处理,判断一下当前h函数传递是否传递了data,如果data是数组或者原始值时,那么data就是children,这也提高了方法的灵活性,最后通过_createElement来生成vnode
    export function createElement ( context: Component, // vue实例 tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean ): VNode | Array<VNode> { 
          // 当data是数组或者原始值的时候,data其实就是children if (Array.isArray(data) || isPrimitive(data)) { 
          normalizationType = children children = data data = undefined } // 当是用户穿入的render函数的时候,会将normalizationType定位为常量ALWAYS_NORMALIZE(2) if (isTrue(alwaysNormalize)) { 
          normalizationType = ALWAYS_NORMALIZE } // VNode是在_createElement中创建的 return _createElement(context, tag, data, children, normalizationType) } 

5)_createElement生成vnode(src\core\vdom\create-element.js)

  • 该函数中主要做了以下处理:
    • a) 将传入的Children规范成VNode类型
    • b) 规范化children后,创建VNode实例
  • 规范化children会用到两个函数normalizeChildrensimpleNormalizeChildren
  • simpleNormalizeChildren:调用场景是 render 函数当函数是编译生成的;理论上编译的children已经是VNode类型,但如果children中包含组件,并且这个组件是函数式组件时,返回的就是一个数组而非一个根节点,则使用该方法将二维数组拍平;(函数式组件已经normalize了他们的children
  • normalizeChildren:用于编译slotv-for的时候产生嵌套数组,或者是用户手写的render函数,会调用该方法
    // (src\core\vdom\create-element.js) // 如果children中包含组件,并且这个组件是函数式组件时,就会做处理 // 因为函数式组件已经normalize了他们的children 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 } export function normalizeChildren (children: any): ?Array<VNode> { 
          // children是原始值,通过createTextVNode转换为文本节点 // normalizeArrayChildren把多维数组通过递归的方式转换为一维数组 return isPrimitive(children) ? [createTextVNode(children)] : Array.isArray(children) ? normalizeArrayChildren(children) : undefined } 
    • 最后就是通过对tag类型的判断,来创建VNode节点;

VNode 的处理过程

1)vm._update(判断是否首次渲染,执行__patch__函数)

  • 前面由vm._render生成了VNode,接下来会通过vm._update将虚拟DOM转化为真实DOM,并渲染到页面中
  • 下面是vm._update方法,在该方法中主要是调用了__patch__方法
    • a) 首先通过vm._vnode获取prevVnode
    • b) 如果prevVnode不存在,说明是首次渲染,调用vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */),并将其返回结果放在vm.$el;并在初始化之后,将最新的虚拟dom存储到vm._vnode
    • c) 如果存在,则执行vm.__patch__(prevVnode, vnode),对两个vnode进行比较,并将返回结果放于vm.$el
// src\core\instance\lifecycle.js Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) { 
    const vm: Component = this const prevEl = vm.$el const prevVnode = vm._vnode const restoreActiveInstance = setActiveInstance(vm) vm._vnode = vnode // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. if (!prevVnode) { 
    // initial render vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */) } else { 
    // updates vm.$el = vm.__patch__(prevVnode, vnode) } ... } 

2) vm.__patch__(判断是否浏览器环境,执行patch

  • 在上面__update__中,我们看到真正处理vnode的地方是在patch中,下面看一下patch方法的实现
  • a) __patch__函数
    // src\platforms\web\runtime\index.js` // __patch__ 该函数是将虚拟DOM转为真实DOM, noop是一个空函数 Vue.prototype.__patch__ = inBrowser ? patch : noop 
  • b)patch函数
    //src\platforms\web\runtime\patch.js export const patch: Function = createPatchFunction({ 
          nodeOps, modules }) 

3) createPatchFunction(返回patch函数)

  • a) 挂载cbs节点的属性/事件/样式操作的钩子函数
  • b) nodeOps:一些dom的操作函数,有createElementremoveChildtagNamedom操作函数
  • c) modules:可以看到每个模块完成某个功能,属性和类、监听器、dom属性、样式的创建和更新、指令更新以及其他操作
    // src\core\vdom\patch.js export function createPatchFunction (backend) { 
          let i, j const cbs = { 
         } const { 
          modules, nodeOps } = backend // hooks中定义了一些钩子函数 for (i = 0; i < hooks.length; ++i) { 
          // cbs["update"] = []; 一个钩子函数可能对应多个处理函数  cbs[hooks[i]] = [] for (j = 0; j < modules.length; ++j) { 
          if (isDef(modules[j][hooks[i]])) { 
          // cbs["update"] = [updateAttrs, updateClass, update...] cbs[hooks[i]].push(modules[j][hooks[i]]) } } } // 一些辅助函数的定义 return function patch (oldVnode, vnode, hydrating, removeOnly) { 
          ... } 

4) createPatchFunctionp返回patch函数(返回vnodedom元素)

  • a) 新的vnode存在,老的vnode不存在,则执行老的vnodedestory钩子函数
  • b) 老的vnode不存在(即$mount中没有挂载的元素),则创建新的vnode,由于没有挂载的元素,所以创建的vnode只存在于内存,不进行挂载
  • c) 获取isRealElement = oldVnode.nodeTypeisRealElement不是真实dom,且新的vnode与老的vnode都存在且相等,则执行patchVnode
  • d) isRealElement为一个真实dom(首次渲染),则通过oldVnode = emptyNodeAt(oldVnode)生成一个虚拟dom
  • e) 通过createElm创建dom节点并插入到对应位置的过程
  • f)invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch),触发insertedVnodeQueue队列中新插入的VNode的钩子函数
 return function patch (oldVnode, vnode, hydrating, removeOnly) { 
    // 新的VNode不存在 if (isUndef(vnode)) { 
    // 老的VNode存在,执行Destoey钩子函数 if (isDef(oldVnode)) invokeDestroyHook(oldVnode) return } let isInitialPatch = false // 存储新插入的VNode的节点的队列 const insertedVnodeQueue = [] // 老的VNode不存在 if (isUndef(oldVnode)) { 
    // empty mount (likely as component), create new root element // mount中没有挂载的元素 isInitialPatch = true // 创建新的VNode,因为$mount没有传入挂载的元素,所以生成的vnode只是存在于内存中,并不进行挂载 createElm(vnode, insertedVnodeQueue) } else { 
    // 新的和老的VNode都存在,更新 const isRealElement = isDef(oldVnode.nodeType) // 判断参数1是否是真实DOM,不是真实DOM if (!isRealElement && sameVnode(oldVnode, vnode)) { 
    // 更新操作,diff算法 // patch existing root node patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly) } else { 
    // 如果oldVNode是真是节点,说明是首次渲染 if (isRealElement) { 
    // mounting to a real element // check if this is server-rendered content and if we can perform // a successful hydration. if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) { 
    oldVnode.removeAttribute(SSR_ATTR) hydrating = true } if (isTrue(hydrating)) { 
    if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { 
    invokeInsertHook(vnode, insertedVnodeQueue, true) return oldVnode } else if (process.env.NODE_ENV !== 'production') { 
    warn( 'The client-side rendered virtual DOM tree is not matching ' + 'server-rendered content. This is likely caused by incorrect ' + 'HTML markup, for example nesting block-level elements inside ' + '

, or missing . Bailing hydration and performing '

+ 'full client-side render.' ) } } // either not server-rendered, or hydration failed. // create an empty node and replace it // 将oldVNode转换为一个VNode,存储在oldVNode中 oldVnode = emptyNodeAt(oldVnode) } // replacing existing element const oldElm = oldVnode.elm const parentElm = nodeOps.parentNode(oldElm) // create new node // 创建DOM节点 createElm( vnode, insertedVnodeQueue, // extremely rare edge case: do not insert if old element is in a // leaving transition. Only happens when combining transition + // keep-alive + HOCs. (#4590) oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ) // update parent placeholder node element, recursively if (isDef(vnode.parent)) { let ancestor = vnode.parent const patchable = isPatchable(vnode) while (ancestor) { for (let i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](ancestor) } ancestor.elm = vnode.elm if (patchable) { for (let i = 0; i < cbs.create.length; ++i) { cbs.create[i](emptyNode, ancestor) } // #6513 // invoke insert hooks that may have been merged by create hooks. // e.g. for directives that uses the "inserted" hook. const insert = ancestor.data.hook.insert if (insert.merged) { // start at index 1 to avoid re-invoking component mounted hook for (let i = 1; i < insert.fns.length; i++) { insert.fns[i]() } } } else { registerRef(ancestor) } ancestor = ancestor.parent } } // destroy old node if (isDef(parentElm)) { removeVnodes([oldVnode], 0, 0) } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode) } } } // 触发insertedVnodeQueue队列中新插入的VNode的钩子函数 // 如果没有挂载至dom树上,那么不会触发它的钩子函数 invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) return vnode.elm } }

5) patchVnode(新旧Vnode对比)

  • 对比新旧Vnode,找到差异,并更新dom在下面的代码都有标注对应的注释
  • 下附新旧节点对比流程图
    在这里插入图片描述

     function patchVnode ( oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly ) { 
          if (oldVnode === vnode) { 
          return } if (isDef(vnode.elm) && isDef(ownerArray)) { 
          // clone reused vnode vnode = ownerArray[index] = cloneVNode(vnode) } const elm = vnode.elm = oldVnode.elm if (isTrue(oldVnode.isAsyncPlaceholder)) { 
          if (isDef(vnode.asyncFactory.resolved)) { 
          hydrate(oldVnode.elm, vnode, insertedVnodeQueue) } else { 
          vnode.isAsyncPlaceholder = true } return } // reuse element for static trees. // note we only do this if the vnode is cloned - // if the new node is not cloned it means the render functions have been // reset by the hot-reload-api and we need to do a proper re-render. if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { 
          vnode.componentInstance = oldVnode.componentInstance return } let i const data = vnode.data // 获取vnode中data,再获取data中的hook,找hook中的prepatch,如果存在执行这个钩子 // 也就是执行用户传递过来的钩子函数 if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { 
          i(oldVnode, vnode) } // 获取新旧节点的子节点 const oldCh = oldVnode.children const ch = vnode.children if (isDef(data) && isPatchable(vnode)) { 
          // 调用cbs中的钩子函数,操作节点的属性/样式/事件 // 遍历所有模块中的update函数,来更新节点的属性/样式/事件 ,这是模块中提供的钩子函数 for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode) // 用户的自定义钩子 if (isDef(i = data.hook) && isDef(i = i.update)) i(oldVnode, vnode) } // 新节点没有文本 if (isUndef(vnode.text)) { 
          // 新节点和老节点都有子节点 // 对子节点进行diff操作,调用updateChildren if (isDef(oldCh) && isDef(ch)) { 
          if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) } else if (isDef(ch)) { 
          // 新的有子节点,老的没有子节点 if (process.env.NODE_ENV !== 'production') { 
          // 会检查新的子节点是否有重复的key,如果有重复的key,那么会报警告 checkDuplicateKeys(ch) } // 先清空老节点dom的文本内容,然后为当前的dom节点加入子节点 if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '') addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) } else if (isDef(oldCh)) { 
          // 老节点有子节点,新的没有子节点 // 删除老节点的子节点 removeVnodes(oldCh, 0, oldCh.length - 1) } else if (isDef(oldVnode.text)) { 
          // 老节点有文本,新节点没有文本 // 清空老节点的文本内容 nodeOps.setTextContent(elm, '') } } else if (oldVnode.text !== vnode.text) { 
          // 新老节点都有文本节点 // 修改文本 nodeOps.setTextContent(elm, vnode.text) } if (isDef(data)) { 
          // 获取data下hook下的postpatch来执行,说明patch过程执行完毕了 if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode) } } 

6) updateChidren(新老子节点对比)

  • 对比新老子节点,找到子节点差异,更新dom树,(在下面的代码都有标注对应的注释
  • 上面一共涉及8个变量:
    • 老节点开始索引oldStartIdx;新节点开始索引newStartIdx
    • 老节点结束索引oldEndIdx;新节点结束索引newEndIdx
    • 老的开始节点oldStartVnode;老的结束节点oldEndVnode
    • 新的开始节点newStartVnode;新的结束节点newEndVnode
  • 在上面的循环对比结束,还会进行以下判断:
    • a) 新节点比老节点多,把剩下的新节点插入到老的节点后面
    • b) 老节点比新节点多,则把多余的老节点进行删除
  • 下面是updateChildren 中对比的主流程图:
    在这里插入图片描述

  • 源码如下:
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) { 
    let oldStartIdx = 0 let newStartIdx = 0 let oldEndIdx = oldCh.length - 1 let oldStartVnode = oldCh[0] let oldEndVnode = oldCh[oldEndIdx] let newEndIdx = newCh.length - 1 let newStartVnode = newCh[0] let newEndVnode = newCh[newEndIdx] let oldKeyToIdx, idxInOld, vnodeToMove, refElm // removeOnly is a special flag used only by 
    // to ensure removed elements stay in correct relative positions // during leaving transitions const canMove = !removeOnly if (process.env.NODE_ENV !== 'production') { 
    checkDuplicateKeys(newCh) } // diff算法 // 当新节点和纠结点都没有遍历完成 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { 
    // 判断老节点是否有值,没有值,取下一个老的节点 if (isUndef(oldStartVnode)) { 
    oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left } else if (isUndef(oldEndVnode)) { 
    // 老的结束节点是否有值,没有值,获取前一个节点作为结束节点 oldEndVnode = oldCh[--oldEndIdx] } else if (sameVnode(oldStartVnode, newStartVnode)) { 
    // 老的开始节点与新的开始节点相同 // 直接将该VNode节点进行patchVnode patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) // 获取下一组开始节点 oldStartVnode = oldCh[++oldStartIdx] newStartVnode = newCh[++newStartIdx] } else if (sameVnode(oldEndVnode, newEndVnode)) { 
    // 老的结束节点与新的结束节点相同 // 直接将该VNode节点进行patchVnode patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx) // 获取下一组结束节点 oldEndVnode = oldCh[--oldEndIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldStartVnode, newEndVnode)) { 
    // Vnode moved right // oldStartVnode与newEndVnode相同(sameVnode) // 进行patchVnode, 把oldStartVnode移动到最后 patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx) canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm)) // 移动下标,获取下一组节点 oldStartVnode = oldCh[++oldStartIdx] newEndVnode = newCh[--newEndIdx] } else if (sameVnode(oldEndVnode, newStartVnode)) { 
    // Vnode moved left // oldEndVnode与newStartVnode相同(sameVnode) // 进行patchVnode, 把oldEndvnode移动到最前面 patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm) oldEndVnode = oldCh[--oldEndIdx] newStartVnode = newCh[++newStartIdx] } else { 
    // 以上四种情况都不满足 // newStartVnode依次和旧的节点比较 // 从新的节点开头取一个,去老节点中查找相同节点 // 先找新开始节点的key和老节点相同的索引,如果没找到再通过sameVnode找 if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx) // 如果没有找到 if (isUndef(idxInOld)) { 
    // New element // 创建节点并插入到最前面 createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } else { 
    // 获取要移动的老节点 vnodeToMove = oldCh[idxInOld] // 如果使用newStartVnode找到相同的老节点 if (sameVnode(vnodeToMove, newStartVnode)) { 
    // 执行patchVnode,并且将找到的旧节点移动到最前面 patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx) oldCh[idxInOld] = undefined canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm) } else { 
    // same key but different element. treat as new element // 如果key相同,但是是不同的元素,创建新元素 createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx) } } newStartVnode = newCh[++newStartIdx] } } // 当结束时oldStartIdx > oldEndIdx,纠结点被遍历完,但是新节点还没有 if (oldStartIdx > oldEndIdx) { 
    // 说明新节点比老节点多,把剩下的新节点插入到老的节点后面 refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) } else if (newStartIdx > newEndIdx) { 
    // 当结束时newStartIdx > newEndIdx,新节点遍历完,但是旧节点还没有,则将老节点remove removeVnodes(oldCh, oldStartIdx, oldEndIdx) } } 
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/215713.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 下午1:36
下一篇 2026年3月18日 下午1:37


相关推荐

  • “此工作站和主域间的信任关系失败”之解决

    “此工作站和主域间的信任关系失败”之解决某虚拟化的域控制器出现严重故障以至于不可修复 故使用之前 Hyper V 中导出的备份恢复了域控制器 恢复后基本功能正常 但部分工作站登录时提示 此工作站和主域间的信任关系失败 解决方案 0 必须确保故障工作站没有其他的问题 如网络连接故障 DNS 设置错误等 1 在不能登录域的工作站上 使用工作站本地的管理员用户登录系统 2 在工作站上打开 powershell 输入 Reset

    2026年3月18日
    1
  • 学会使用IDEA断点调试工具

    学会使用IDEA断点调试工具IDEA 学会使用断点调试

    2026年3月26日
    2
  • Odin Inspector 系列教程 — SearchableAttribute「建议收藏」

    Odin Inspector 系列教程 — SearchableAttribute「建议收藏」通过添加SearchableAttribute特性为其添加一个搜索框,可用于搜索对应的类或其子类的成员,但目前不可用于字典类型。imageusingSirenix.OdinInspector;usingSystem;usingSystem.Collections.Generic;usingUnityEngine;publicclassSearchableExam…

    2022年7月21日
    29
  • linux tree命令,Linux tree命令实例详解

    linux tree命令,Linux tree命令实例详解关于treetree以树状格式列出目录的内容。这是一个非常简洁实用的程序,您可以在命令行中使用它来查看文件系统的结构。描述tree是一个递归目录列表程序,它生成一个深度缩进的文件列表(如果设置了LS_COLORS环境变量,则会着色)并输出为tty。如果没有参数,树将列出当前目录中的文件。当给出目录参数时,树依次列出在给定目录中找到的所有文件和/或目录。树然后返回列出的文件和/或目录的总数。…

    2022年7月25日
    8
  • linux 查看文件夹大小「建议收藏」

    linux 查看文件夹大小「建议收藏」最简单的查看方法可以使用ls-ll、ls-lh命令进行查看,当使用ls-ll,会显示成字节大小,而ls-lh会以KB、MB等为单位进行显示,这样比较直观一些。 通过命令du-h–max-depth=1*,可以查看当前目录下各文件、文件夹的大小,这个比较实用。 查询当前目录总大小可以使用du-sh,其中s代表统计汇总的意思,即只输出一个总和大小。…

    2025年7月15日
    5
  • 姿态估计与行为识别(行为检测、行为分类)的区别[通俗易懂]

    姿态估计与行为识别(行为检测、行为分类)的区别[通俗易懂]姿态估计和行为识别作为计算机视觉的两个领域,对于新人来说,较为容易弄混姿态估计和行为识别两个概念。 姿态估计(PoseEstimation)是指检测图像和视频中的人物形象的计算机视觉技术,可以确定某人的某个身体部位出现在图像中的位置,也就是在图像和视频中对人体关节的定位问题,也可以理解为在所有关节姿势的空间中搜索特定姿势。简言之,姿态估计的任务就是重建人的关节和肢干,其难点主要在于…

    2022年6月21日
    29

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号