剖析vue常见问题(四)之vue中的diff算法

2023-10-27

背景:首先diff算法不是vue的专属,只要采用虚拟dom的框架基本都会采用diff算法,那么为什么要采用diff算法呢以及diff算法的好处是什么呢?我们还以vue为例,从源码层面做下分析,分别说明一下diff算法的必要性(/src/core/instance/lifecycle.js中的mountComponent()方法)、执行方式(/src/core/vdom/patch.js中的patchVnode()方法)以及带来的高效性(/src/core/vdom/patch.js中的updateChildren()方法)。

1.diff算法的必要性分析,参考源码:src/core/instance/lifecycle.js中的mountComponent()方法,源码如下:

/*挂载组件*/
export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el
  if (!vm.$options.render) {
    /*render函数不存在的时候创建一个空的VNode节点*/
    vm.$options.render = createEmptyVNode
    if (process.env.NODE_ENV !== 'production') {
      /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        vm.$options.el || el) {
        warn(
          'You are using the runtime-only build of Vue where the template ' +
          'compiler is not available. Either pre-compile the templates into ' +
          'render functions, or use the compiler-included build.',
          vm
        )
      } else {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  /*触发beforeMount钩子*/
  callHook(vm, 'beforeMount')

  /*updateComponent作为Watcher对象的getter函数,用来依赖收集*/
  let updateComponent
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
    updateComponent = () => {
      const name = vm._name
      const id = vm._uid
      const startTag = `vue-perf-start:${id}`
      const endTag = `vue-perf-end:${id}`

      mark(startTag)
      const vnode = vm._render()
      mark(endTag)
      measure(`${name} render`, startTag, endTag)

      mark(startTag)
      vm._update(vnode, hydrating)
      mark(endTag)
      measure(`${name} patch`, startTag, endTag)
    }
  } else {
    updateComponent = () => {
      vm._update(vm._render(), hydrating)
    }
  }

  /*这里对该vm注册一个Watcher实例,Watcher的getter为updateComponent函数,用于触发所有渲染所需要用到的数据的getter,进行依赖收集,该Watcher实例会存在所有渲染所需数据的闭包Dep中*/
  vm._watcher = new Watcher(vm, updateComponent, noop)
  hydrating = false

  // manually mounted instance, call mounted on self
  // mounted is called for render-created child components in its inserted hook
  if (vm.$vnode == null) {
    /*标志位,代表该组件已经挂载*/
    vm._isMounted = true
    /*调用mounted钩子*/
    callHook(vm, 'mounted')
  }
  return vm
}

从源码中我们可以得出mountComponent()是由用户$mount()时调用的,所以一个组件会调用一次$mount(),所以也会创建对应的watcher,但是一个组件中可能存在多个data的key的使用,这时候只存在一个watcher它如果想要明确知道具体的变化的key就需要使用diff算法进行一次新旧两个虚拟dom的比较就可以知道具体变化的地方了。

2.diff算法的执行方式,参考源码:/src/core/vdom/patch.js中的patchVnode()方法,源码如下:

  /*patch VNode节点*/
  function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
    /*两个VNode节点相同则直接返回*/
    if (oldVnode === vnode) {
      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.
    /*
      如果新旧VNode都是静态的,同时它们的key相同(代表同一节点),
      并且新的VNode是clone或者是标记了once(标记v-once属性,只渲染一次),
      那么只需要替换elm以及componentInstance即可。
    */
    if (isTrue(vnode.isStatic) &&
        isTrue(oldVnode.isStatic) &&
        vnode.key === oldVnode.key &&
        (isTrue(vnode.isCloned) || isTrue(vnode.isOnce))) {
      vnode.elm = oldVnode.elm
      vnode.componentInstance = oldVnode.componentInstance
      return
    }
    let i
    const data = vnode.data
    if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) {
      /*i = data.hook.prepatch,如果存在的话,见"./create-component componentVNodeHooks"。*/
      i(oldVnode, vnode)
    }
    const elm = vnode.elm = oldVnode.elm
    const oldCh = oldVnode.children
    const ch = vnode.children
    if (isDef(data) && isPatchable(vnode)) {
      /*调用update回调以及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)
    }
    /*如果这个VNode节点没有text文本时*/
    if (isUndef(vnode.text)) {
      if (isDef(oldCh) && isDef(ch)) {
        /*新老节点均有children子节点,则对子节点进行diff操作,调用updateChildren*/
        if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
      } else if (isDef(ch)) {
        /*如果老节点没有子节点而新节点存在子节点,先清空elm的文本内容,然后为当前节点加入子节点*/
        if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
      } else if (isDef(oldCh)) {
        /*当新节点没有子节点而老节点有子节点的时候,则移除所有ele的子节点*/
        removeVnodes(elm, oldCh, 0, oldCh.length - 1)
      } else if (isDef(oldVnode.text)) {
        /*当新老节点都无子节点的时候,只是文本的替换,因为这个逻辑中新节点text不存在,所以直接去除ele的文本*/
        nodeOps.setTextContent(elm, '')
      }
    } else if (oldVnode.text !== vnode.text) {
      /*当新老节点text不一样时,直接替换这段文本*/
      nodeOps.setTextContent(elm, vnode.text)
    }
    /*调用postpatch钩子*/
    if (isDef(data)) {
      if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
    }
  }

我们知道patchVnode()是diff开始的地方,重点在新旧虚拟节点的比较部分,通过代码可以看出对比策略为:深度优先,同层比较,重点性能还是在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, elmToMove, refElm

    // removeOnly is a special flag used only by <transition-group>
    // to ensure removed elements stay in correct relative positions
    // during leaving transitions
    const canMove = !removeOnly

    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)) {
        /*前四种情况其实是指定key的时候,判定为同一个VNode,则直接patchVnode即可,分别比较oldCh以及newCh的两头节点2*2=4种情况*/
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        /*
          生成一个key与旧VNode的key对应的哈希表(只有第一次进来undefined的时候会生成,也为后面检测重复的key值做铺垫)
          比如childre是这样的 [{xx: xx, key: 'key0'}, {xx: xx, key: 'key1'}, {xx: xx, key: 'key2'}]  beginIdx = 0   endIdx = 2  
          结果生成{key0: 0, key1: 1, key2: 2}
        */
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        /*如果newStartVnode新的VNode节点存在key并且这个key在oldVnode中能找到则返回这个节点的idxInOld(即第几个节点,下标)*/
        idxInOld = isDef(newStartVnode.key) ? oldKeyToIdx[newStartVnode.key] : null
        if (isUndef(idxInOld)) { // New element
          /*newStartVnode没有key或者是该key没有在老节点中找到则创建一个新的节点*/
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
          newStartVnode = newCh[++newStartIdx]
        } else {
          /*获取同key的老节点*/
          elmToMove = oldCh[idxInOld]
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !elmToMove) {
            /*如果elmToMove不存在说明之前已经有新节点放入过这个key的Dom中,提示可能存在重复的key,确保v-for的时候item有唯一的key值*/
            warn(
              'It seems there are duplicate keys that is causing an update error. ' +
              'Make sure each v-for item has a unique key.'
            )
          }
          if (sameVnode(elmToMove, newStartVnode)) {
            /*如果新VNode与得到的有相同key的节点是同一个VNode则进行patchVnode*/
            patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
            /*因为已经patchVnode进去了,所以将这个老节点赋值undefined,之后如果还有新节点与该节点key相同可以检测出来提示已有重复的key*/
            oldCh[idxInOld] = undefined
            /*当有标识位canMove实可以直接插入oldStartVnode对应的真实Dom节点前面*/
            canMove && nodeOps.insertBefore(parentElm, newStartVnode.elm, oldStartVnode.elm)
            newStartVnode = newCh[++newStartIdx]
          } else {
            // same key but different element. treat as new element
            /*当新的VNode与找到的同样key的VNode不是sameVNode的时候(比如说tag不一样或者是有不一样type的input标签),创建一个新的节点*/
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
            newStartVnode = newCh[++newStartIdx]
          }
        }
      }
    }
    if (oldStartIdx > oldEndIdx) {
      /*全部比较完成以后,发现oldStartIdx > oldEndIdx的话,说明老节点已经遍历完了,新节点比老节点多,所以这时候多出来的新节点需要一个一个创建出来加入到真实Dom中*/
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
      /*如果全部比较完成以后发现newStartIdx > newEndIdx,则说明新节点已经遍历完了,老节点多余新节点,这个时候需要将多余的老节点从真实Dom中移除*/
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
    }
  }

从上可以看出,两个节点的对比方法包括假设首尾节点可能相同的4次比对尝试,如果都没有才会按照普通遍历,高效性在这里。

综上可以得出结论如下:

1.diff算法是虚拟dom的产物:通过新旧虚拟dom做对比(即diff),将变化更新在真是dom中,通过diff高效的对比,时间复杂度由O(n^3)降低到O(n)。

2.vue2中降低了watcher粒度,每个组件只有一个watcher与之对应,引入了diff算法可以精确找到发生变化的地方。

3.vue中diff执行的时刻是组件实例执行其更新函数时,它会对比上一次渲染结果oldVnode和新的渲染结果newVnode,这个过程就是我们说的打补丁patch。

4.diff算法整体策略:深度优先、同层比较;两个节点之间的比较会根据他们是否有子节点或者文本节点做不同操作;比较两组子节点是算法的重点部分,即首先假设首尾节点可能相同做4次尝试对比,如果没有找到相同的节点才会走遍历查找,查找结束后再分情况处理剩下的节点;借助key可以快速准确的找到相同节点,所以整个patch过程很高效。

以上为vue的diff算法,希望对你理解有所帮助,转载请标明出处,谢谢。

vue的key可以参考我的上一篇文章:剖析vue常见问题(三)之vue中key的作用和原理

react的diff算法和key可以参考文章:React的diff算法和key属性简介

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

剖析vue常见问题(四)之vue中的diff算法 的相关文章

  • 现代版本的 WinDiff? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 VB6附带WinDiff 是否有免费的现代版本的 WinDiff 可以忽略大小写 除了最新 SDK 中
  • 当使用 svn cp 或 svn mv 时,如何使 svn diff 生成补丁将应用的文件?

    场景是 svn cp 或 mv 某些文件 修改该文件 svn diff gt 我的补丁 在其他机器上 相同的工作副本 但没有更改 尝试应用我的补丁 失败 gt 尝试修改不存在的文件 在这种情况下 如何使 svn diff 生成适用于补丁的补
  • 使用 Linux 比较两个不同的 url

    我是否可以比较两个不同的网址 网站 而无需先使用下载文件wget或者先做类似的事情 我尝试了以下操作 但收到以下错误 root desktop diff http www example net index php http www exa
  • 如何获取 git diff 文件,并将其应用到作为同一存储库副本的本地分支?

    我有一个由同事创建的 diff 文件 并且希望将该 diff 文件中列出的更改应用到完全相同存储库的本地分支 我无权访问用于生成此差异文件的该工作人员的电脑或分支 显然 我可以逐行重新输入所有内容 但我不想让系统遭受人为错误 做到这一点最简
  • 使用 git diff,如何获取添加和修改的行号?

    假设我有一个文本文件 alex bob matrix will be removed git repo 我已将其更新为 alex new line here another new line bob matrix git 在这里 我添加了行
  • 如何获取两个 git 分支之间不同提交的列表?

    我想查看两个分支之间仅非常见提交的列表 我怎样才能得到这样的输出 基本上是一个git diff y master new feature两个分支之间的总结 master new feature xxx Jan 1st 2018 initia
  • 关于如何构建 HTML Diff 工具的建议?

    In 这个帖子 https stackoverflow com questions 48669 are there any tools out there to compare the structure of 2 web pages我问是
  • 你能帮助解释我的 svn diff 输出吗?

    我正在使用 SVN DIFF 比较两个文件夹 一个在分支中 一个在主干中 目的是确定更改列表 然后我对分支中的文件进行了一些更改 但输出显示我已经在主干中修改了它们 为什么会出现这种情况 有没有更好的命令来获取我正在寻找的结果 我现在使用的
  • 检查服务器上文件的差异

    我的机器上有一个存储库的工作副本 并且我知道它已在服务器上更新 我想知道如何通过使用来获取新版本和工作副本中的版本之间的差异svn命令行参数 我有办法做到这一点吗 工作副本是修订版 BASE 存储库中的最新副本是修订版 HEAD 这会将您的
  • 在 C# 中创建大型二进制文件的增量差异补丁

    我正在寻找一种创建大型二进制文件 VMWare 虚拟磁盘文件 的 Delta Diff 补丁的方法 是否有 C 中的实现或 NET Framework 中的任何有用的方法 任何帮助表示赞赏 谢谢 rAyt bsdiff http www d
  • 如何正确区分树(即嵌套的字符串列表)?

    我正在使用由嵌套字符串列表组成的数据类型的在线编辑器 请注意 如果每次更改单个值时我都要传输整个结构 那么流量可能会变得难以忍受 所以 为了减少流量 我想到了应用 diff 工具 问题是 如何找到并报告两棵树的差异 例如 ah bh ha
  • 如何使用 diff 排除多行模式?

    我想对两个 xml 文件进行比较 但忽略 2 3 行模式 例如 假设我想在比较下面的 xml 格式时忽略可用性和价格 这是我到目前为止所拥有的 diff I
  • 是否可以更改 Mercurial 中的默认 diff 工具?

    每次我做一个hg diff file ext我最终使用了控制台差异应用程序 我想使用 Kdiff3 或 WinMerge 我使用的是 Windows 有办法改变吗 我在 Mercurial 文档中找不到参考 我不是在谈论合并 我已经使用 M
  • diff 文件仅比较每行的前 n 个字符

    我有2个文件 我们将它们称为 md5s1 txt 和 md5s2 txt 两者都包含a的输出 find type f print0 xargs 0 md5sum sort gt md5s txt 不同目录下的命令 许多文件被重命名 但内容保
  • 如何制作和应用SVN补丁?

    我想制作一个SVN类型的补丁文件httpd conf这样我就可以轻松地将其应用到其他主机上 If I do cd root diff Naur etc httpd conf httpd conf original etc httpd con
  • 有一种简单的方法可以忽略时间戳来区分日志文件吗?

    我需要比较两个日志文件 但忽略每行的时间戳部分 确切地说是前 12 个字符 有没有一个好的工具 或者一个聪明的 awk 命令 可以帮助我 根据您使用的 shell 您可以改变方法 Blair https stackoverflow com
  • 如何使用 vim 作为寻呼机设置彩色 git diff

    我无法配置 git 来遵循我的请求 使用 vim 作为差异分页器 在交互模式下添加文件时保留差异颜色 My gitconfig setup color ui auto diff false pager diff vim 通过此配置 交互模式
  • 如何从父克隆中过去的提交中获取 git 子模块的关联提交 ID?

    有没有一种方法 除了实际检查父提交之外 还可以根据父克隆中的提交 ID 确定子模块的 SHA 1 提交 ID 我知道我能找到现在与 SHA 1 关联git submodule 这是一个例子 我有一个带有单个子模块的克隆foo上个月情况发生了
  • MongoDB 中两个集合之间的 Diff()

    我做过研究 如果这是一个重复的问题 我很抱歉 但其他问题的解决方案并不适合我 因此 我提出了一个新问题 使用 Javascript 比较两个集合的最佳方法是什么 我有数千个这样的 Mongo 文档格式的标头 url google com h
  • 从“git diff”中排除文件

    我正在尝试排除一个文件 db irrelevant php 来自 Git diff 我尝试将文件放入db子目录名为 gitattributes与线irrelevant php diff我还尝试创建一个名为 git info attribut

随机推荐

  • CUDA并行算法系列之FFT快速卷积

    CUDA并行算法系列之FFT快速卷积 卷积定义 在维基百科上 卷积定义为 离散卷积定义为 0 1 2 3 和 0 1 2 的卷积例子如下图所示 Python实现 直接卷积 根据离散卷积的定义 用Python实现 def conv a b N
  • RNN, LSTM, GRU模型结构详解(看这一篇就够了)

    RNN和LSTM讲解超详细的文章 https zhuanlan zhihu com p 32085405 GRU超详解文章 https zhuanlan zhihu com p 32481747
  • jupyter notebook 导出 markdown文件格式

    jupyter notebook 导出 markdown文件格式 原本jupyter notebook 里面自带的可以选择导出为markdown格式 但是下载之后文件总是打不开 只能另寻他法 方法 第一步 安装nbconvert pip i
  • C++类和对象的基本概念

    目录 1 c和c 中struct的区别 2 类的封装 3 类的访问权限 1 c和c 中struct的区别 c语言中结构体中不能存放函数 也就是数据 属性 和行为 方 法 是分离的 c 中结构体中是可以存放函数的 也就是数据 属性 和行为 方
  • Linux文件编程常用函数详解——fcntl()函数

    fcntl 函数 include
  • 智能指针(二):shared_ptr实现原理

    前面讲到auto ptr有个很大的缺陷就是所有权的转移 就是一个对象的内存块只能被一个智能指针对象所拥有 但我们有些时候希望共用那个内存块 于是C 11标准中有了shared ptr这样的智能指针 顾名思义 有个shared表明共享嘛 所以
  • windows升级node版本

    当本地的node版本过低的时候 这就需要升级更高版本来满足开发需求 本文详细教大家如何升级自己需要的node版本 1 官网 下载 Node js 中文网 下载找到需要升级的node版本 下载也默认只有长期支持版本和最新版本 如果满足需求 直
  • 2020 MCM Weekend 2 Problem C,2020美赛C题——完整版题目

    文章目录 Problem C A Wealth of Data Problem Requirements Glossary Data Set Definitions Problem C A Wealth of Data Problem In
  • 测试开发岗需要学习什么样的技能才能满足需求?也许通过阅读各个互联网大厂的JD你会更加清楚

    目录 前言 各大互联网厂关于测试开发的要求 实习 测试开发实习生 测试中心 B站 测试开发实习生 商业技术部 B站 测试开发实习生 直播 B站 测试开发工程师 实习 阿里 游戏测试开发工程师 实习 阿里 测试开发工程师 教育业务 实习 字节
  • 时间序列之指数平滑法(Exponential Smoothing)

    统计中 预测方法除了利用多个影响因素建立回归模型来做预测外 在影响因素复杂 或者是没办法得到相关影响因素的数据信息时 回归模型就无能为力了 如果数据是时间序列上的值 在时间上可能呈现一定的稳态或者规律 利用过去时间区间的值来预测未来值 指数
  • 关于Win2008系统DNS服务器安装配置操作教程

    DNS是因特网的一项核心服务 它作为可以将域名和IP地址相互映射的一个分布式数据库 能够使人更方便的访问互联网 而不用去记住能够被机器直接读取的IP 中文全称 网络协议 地址数串 在win2008系统中要成功安装DNS服务器才能够正常的连接
  • Python工程师的发展前景如何?薪资高吗?5点给你分析齐全

    根据网上的人爆料 2020 互联网大厂校招硕士生的薪资情况 和美团今年的校招信息发布 也是引起一波热潮 许多人看到这些薪资都会感叹一声 那真正处于技术岗位的人员又是另一种看法 同时也激起了许多人想学编程的想法 而目前较为火热的Python也
  • 可视化翻转教学python

    目录 第1关 绘制折线图 第2关 绘制正弦曲线 第3关 绘制指定线型 颜色和标记的正弦曲线 第4关 定义绘制正余弦函数曲线的函数 第5关 绘制坐标轴并设置范围 第1关 绘制折线图 显示绘制结果 plt show 用于显示绘制的结果 无参数
  • 华为OD机试 - 报数问题(Java)

    题目描述 有n个人围成一圈 顺序排号为1 n 从第一个人开始报数 从1到3报数 凡报到3的人退出圈子 问最后留下的是原来第几号的那位 输入描述 输入人数n n lt 1000 输出描述 输出最后留下的是原来第几号 用例 输入 2 输出 2
  • PHP 密码长度至少为8,且必须包含大小写字母/数字/符号任意三者组合

    密码长度至少为8 且必须包含大小写字母 数字 符号任意三者组合 public function rexCheckPassword pwd 12345678aaA 8 20 位 字母 数字 字符 密码必须包含大小写字母 数字 符号任意两者组合
  • 程序员必知的设计模式七大原则

    文章目录 设计模式的目的 1 单一职责原则 1 1 单一职责原则注意事项和细节 2 接口隔离原则 2 1 接口隔离原则例子 3 依赖倒转原则 3 1 什么是依赖 3 2 依赖关系传递的三种方式 1 接口传递 依赖 2 构造方法传递 组合 3
  • 用U深度启动U盘清除系统登入密码

    先添加一块硬盘 修改启动顺序 选择windows密码破解工具 选择选项1 出现了许多硬盘 一个一个去试SAM在那个硬盘 最后发现在硬盘2 出现以下界面 选择第一个用户 按y键保存并退出 在按esc键一直退到以下界面 输入r退出关闭计算机 把
  • Pytorch中常见transform的使用

    本次实验练习了pytorch中数据的读取 Dataset类的使用 以及transform模块的使用 一 Pytorch简介 PyTorch是一个开源的Python机器学习库 基于Torch 用于自然语言处理等应用程序 2017年1月 由Fa
  • python实战因子分析和主成分分析

    机器学习中 因子分析和主成分分析是模型降维的两种最常用方法 因子分析基础概念 因子分析是一种统计方法 可用于描述观察到的相关变量之间的变异性 即潜在的未观察到的变量数量可能更少 称为因子 例如 六个观察变量的变化可能主要反映了两个未观察 基
  • 剖析vue常见问题(四)之vue中的diff算法

    背景 首先diff算法不是vue的专属 只要采用虚拟dom的框架基本都会采用diff算法 那么为什么要采用diff算法呢以及diff算法的好处是什么呢 我们还以vue为例 从源码层面做下分析 分别说明一下diff算法的必要性 src cor