我的编程空间,编程开发者的网络收藏夹
学习永远不晚

虚拟DOM是什么

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

虚拟DOM是什么

这篇文章主要介绍了虚拟DOM是什么的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇虚拟DOM是什么文章都会有所收获,下面我们一起来看看吧。

虚拟DOM是什么

Snabbdom 是一个虚拟 DOM 实现库,推荐的原因一是代码比较少,核心代码只有几百行;二是 Vue 就是借鉴此项目的思路来实现虚拟 DOM 的;三是这个项目的设计/实现和扩展思路值得参考。

snabb /snab/,瑞典语,意思是快速的。

调整好舒服的坐姿,打起精神我们要开始啦~ 要学习虚拟 DOM,我们得先知道 DOM 的基础知识和用 JS 直接操作 DOM 的痛点在哪里。

DOM 的作用和类型结构

DOM(Document Object Model)是一种文档对象模型,用一个对象树的结构来表示一个 HTML/XML 文档,树的每个分支的终点都是一个节点(node),每个节点都包含着对象。DOM API 的方法让你可以用特定方式操作这个树,用这些方法你可以改变文档的结构、样式或者内容。

DOM 树中的所有节点首先都是一个 NodeNode 是一个基类。ElementTextComment 都继承于它。
换句话说,ElementTextComment 是三种特殊的 Node,它们分别叫做 ELEMENT_NODE,
TEXT_NODECOMMENT_NODE,代表的是元素节点(HTML 标签)、文本节点和注释节点。其中 Element 还有一个子类是 HTMLElement,那 HTMLElementElement 有什么区别呢?HTMLElement 代表 HTML 中的元素,如:<span><img> 等,而有些元素并不是 HTML 标准的,比如 <svg>。可以用下面的方法来判断这个元素是不是 HTMLElement

document.getElementById('myIMG') instanceof HTMLElement;

为什么需要虚拟 DOM?

浏览器创建 DOM 是很“昂贵”的。来一个经典示例,我们可以通过 document.createElement('p') 创建一个简单的 p 元素,将属性都打印出来康康:

虚拟DOM是什么

可以看到打印出来的属性非常多,当频繁地去更新复杂的 DOM 树时,会产生性能问题。虚拟 DOM 就是用一个原生的 JS 对象去描述一个 DOM 节点,所以创建一个 JS 对象比创建一个 DOM 对象的代价要小很多。

VNode

VNode 就是 Snabbdom 中描述虚拟 DOM 的一个对象结构,内容如下:

type Key = string | number | symbol;

interface VNode {
  // CSS 选择器,比如:'p#container'。
  sel: string | undefined;
  
  // 通过 modules 操作 CSS classes、attributes 等。
  data: VNodeData | undefined; 
  
   // 虚拟子节点数组,数组元素也可以是 string。
  children: Array<VNode | string> | undefined;
  
  // 指向创建的真实 DOM 对象。
  elm: Node | undefined;
  
  
  text: string | undefined;
  
  // 用于给已存在的 DOM 提供标识,在同级元素之间必须唯一,有效避免不必要地重建操作。
  key: Key | undefined;
}

// vnode.data 上的一些设置,class 或者生命周期函数钩子等等。
interface VNodeData {
  props?: Props;
  attrs?: Attrs;
  class?: Classes;
  style?: VNodeStyle;
  dataset?: Dataset;
  on?: On;
  attachData?: AttachData;
  hook?: Hooks;
  key?: Key;
  ns?: string; // for SVGs
  fn?: () => VNode; // for thunks
  args?: any[]; // for thunks
  is?: string; // for custom elements v1
  [key: string]: any; // for any other 3rd party module
}

例如这样定义一个 vnode 的对象:

const vnode = h(
  'p#container',
  { class: { active: true } },
  [
    h('span', { style: { fontWeight: 'bold' } }, 'This is bold'),
    ' and this is just normal text'
]);

我们通过 h(sel, b, c) 函数来创建 vnode 对象。h() 代码实现中主要是判断了 b 和 c 参数是否存在,并处理成 data 和 children,children 最终会是数组的形式。最后通过 vnode() 函数返回上面定义的 VNode 类型格式。

Snabbdom 的运行流程

先来一张运行流程的简单示例图,先有个大概的流程概念:

虚拟DOM是什么

diff 处理是用来计算新老节点之间差异的处理过程。

再来看一段  Snabbdom 运行的示例代码:

import {
  init,
  classModule,
  propsModule,
  styleModule,
  eventListenersModule,
  h,
} from 'snabbdom';

const patch = init([
  // 通过传入模块初始化 patch 函数
  classModule, // 开启 classes 功能
  propsModule, // 支持传入 props
  styleModule, // 支持内联样式同时支持动画
  eventListenersModule, // 添加事件监听
]);

// <p id="container"></p>
const container = document.getElementById('container');

const vnode = h(
  'p#container.two.classes',
  { on: { click: someFn } },
  [
    h('span', { style: { fontWeight: 'bold' } }, 'This is bold'),
    ' and this is just normal text',
    h('a', { props: { href: '/foo' } }, "I'll take you places!"),
  ]
);

// 传入一个空的元素节点。
patch(container, vnode);

const newVnode = h(
  'p#container.two.classes',
  { on: { click: anotherEventHandler } },
  [
    h(
      'span',
      { style: { fontWeight: 'normal', fontStyle: 'italic' } },
      'This is now italic type'
    ),
    ' and this is still just normal text',
    h('a', { props: { href: ''/bar' } }, "I'll take you places!"),
  ]
);

// 再次调用 patch(),将旧节点更新为新节点。
patch(vnode, newVnode);

从流程示意图和示例代码可以看出,Snabbdom 的运行流程描述如下:

  • 首先调用 init() 进行初始化,初始化时需要配置需要使用的模块。比如 classModule 模块用来使用对象的形式来配置元素的 class 属性;eventListenersModule 模块用来配置事件监听器等等。init() 调用后会返回 patch() 函数。

  • 通过 h() 函数创建初始化 vnode 对象,调用 patch() 函数去更新,最后通过 createElm() 创建真正的 DOM 对象。

  • 当需要更新时,创建一个新的 vnode 对象,调用 patch() 函数去更新,经过 patchVnode()updateChildren() 完成本节点和子节点的差异更新。

    Snabbdom 是通过模块这种设计来扩展相关属性的更新而不是全部写到核心代码中。那这是如何设计与实现的?接下来就先来康康这个设计的核心内容,Hooks——生命周期函数。

Hooks

Snabbdom 提供了一系列丰富的生命周期函数也就是钩子函数,这些生命周期函数适用在模块中或者可以直接定义在 vnode 上。比如我们可以在 vnode 上这样定义钩子的执行:

h('p.row', {
  key: 'myRow',
  hook: {
    insert: (vnode) => {
      console.log(vnode.elm.offsetHeight);
    },
  },
});

全部的生命周期函数声明如下:

名称触发节点回调参数
prepatch 开始执行none
initvnode 被添加vnode
create一个基于 vnode 的 DOM 元素被创建emptyVnode, vnode
insert元素被插入到 DOMvnode
prepatch元素即将 patcholdVnode, vnode
update元素已更新oldVnode, vnode
postpatch元素已被 patcholdVnode, vnode
destroy元素被直接或间接得移除vnode
remove元素已从 DOM 中移除vnode, removeCallback
post已完成 patch 过程none

其中适用于模块的是:pre, create,update, destroy, remove, post。适用于 vnode 声明的是:init, create, insert, prepatch, update,postpatch, destroy, remove

我们来康康是如何实现的,比如我们以 classModule 模块为例,康康它的声明:

import { VNode, VNodeData } from "../vnode";
import { Module } from "./module";

export type Classes = Record<string, boolean>;

function updateClass(oldVnode: VNode, vnode: VNode): void {
  // 这里是更新 class 属性的细节,先不管。
  // ...
}

export const classModule: Module = { create: updateClass, update: updateClass };

可以看到最后导出的模块定义是一个对象,对象的 key 就是钩子函数的名称,模块对象 Module 的定义如下:

import {
  PreHook,
  CreateHook,
  UpdateHook,
  DestroyHook,
  RemoveHook,
  PostHook,
} from "../hooks";

export type Module = Partial<{
  pre: PreHook;
  create: CreateHook;
  update: UpdateHook;
  destroy: DestroyHook;
  remove: RemoveHook;
  post: PostHook;
}>;

TS 中 Partial 表示对象中每个 key 的属性都是可以为空的,也就是说模块定义中你关心哪个钩子,就定义哪个钩子就好了。钩子的定义有了,在流程中是怎么执行的呢?接着我们来看 init() 函数:

// 模块中可能定义的钩子有哪些。
const hooks: Array<keyof Module> = [
  "create",
  "update",
  "remove",
  "destroy",
  "pre",
  "post",
];

export function init(
  modules: Array<Partial<Module>>,
  domApi?: DOMAPI,
  options?: Options
) {
  // 模块中定义的钩子函数最后会存在这里。
  const cbs: ModuleHooks = {
    create: [],
    update: [],
    remove: [],
    destroy: [],
    pre: [],
    post: [],
  };

  // ...

  // 遍历模块中定义的钩子,并存起来。
  for (const hook of hooks) {
    for (const module of modules) {
      const currentHook = module[hook];
      if (currentHook !== undefined) {
        (cbs[hook] as any[]).push(currentHook);
      }
    }
  }
  
  // ...
}

可以看到 init() 在执行时先遍历各个模块,然后把钩子函数存到了 cbs 这个对象中。执行的时候可以康康 patch() 函数里面:

export function init(
  modules: Array<Partial<Module>>,
  domApi?: DOMAPI,
  options?: Options
) {
  // ...
  
  return function patch(
  oldVnode: VNode | Element | DocumentFragment,
   vnode: VNode
  ): VNode {
    // ...
    
    // patch 开始了,执行 pre 钩子。
    for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();
    
    // ...
  }
}

这里以 pre 这个钩子举例,pre 钩子的执行时机是在 patch 开始执行时。可以看到 patch() 函数在执行的开始处去循环调用了 cbs 中存储的 pre 相关钩子。其他生命周期函数的调用也跟这个类似,大家可以在源码中其他地方看到对应生命周期函数调用的地方。

这里的设计思路是观察者模式。Snabbdom 把非核心功能分布在模块中来实现,结合生命周期的定义,模块可以定义它自己感兴趣的钩子,然后 init() 执行时处理成 cbs 对象就是注册这些钩子;当执行时间到来时,调用这些钩子来通知模块处理。这样就把核心代码和模块代码分离了出来,从这里我们可以看出观察者模式是一种代码解耦的常用模式。

patch()

接下来我们来康康核心函数 patch(),这个函数是在 init() 调用后返回的,作用是执行 VNode 的挂载和更新,签名如下:

function patch(oldVnode: VNode | Element | DocumentFragment, vnode: VNode): VNode {
    // 为简单起见先不关注 DocumentFragment。
    // ...
}

oldVnode 参数是旧的 VNode 或 DOM 元素或文档片段,vnode 参数是更新后的对象。这里我直接贴出整理的流程描述:

  • 调用模块上注册的 pre 钩子。

  • 如果 oldVnodeElement,则将其转换为空的 vnode 对象,属性里面记录了 elm

    这里判断是不是 Element 是判断 (oldVnode as any).nodeType === 1 是完成的,nodeType === 1 表明是一个 ELEMENT_NODE,定义在 这里。

  • 然后判断 oldVnodevnode 是不是相同的,这里会调用 sameVnode() 来判断:

    function sameVnode(vnode1: VNode, vnode2: VNode): boolean {
      // 同样的 key。
      const isSameKey = vnode1.key === vnode2.key;
      
      // Web component,自定义元素标签名,看这里:
      // https://developer.mozilla.org/zh-CN/docs/Web/API/Document/createElement
      const isSameIs = vnode1.data?.is === vnode2.data?.is;
      
      // 同样的选择器。
      const isSameSel = vnode1.sel === vnode2.sel;
    
      // 三者都相同即是相同的。
      return isSameSel && isSameKey && isSameIs;
    }
    • 如果相同,则调用 patchVnode() 做 diff 更新。

    • 如果不同,则调用 createElm() 创建新的 DOM 节点;创建完毕后插入 DOM 节点并删除旧的 DOM 节点。

  • 调用上述操作中涉及的 vnode 对象中注册的 insert 钩子队列, patchVnode() createElm() 都可能会有新节点插入 。至于为什么这样做,在 createElm() 中会说到。

  • 最后调用模块上注册的 post 钩子。

流程基本就是相同的 vnode 就做 diff,不同的就创建新的删除旧的。接下来先看下 createElm() 是如何创建 DOM 节点的。

createElm()

createElm() 是根据 vnode 的配置来创建 DOM 节点。流程如下:

  • 调用 vnode 对象上可能存在的 init 钩子。

  • 然后分一下几种情况来处理:

    • 解析选择器,得到 idtagclass

    • 调用 document.createElement()document.createElementNS 创建 DOM 节点,并记录到 vnode.elm 中,并根据上一步的结果来设置 idtagclass

    • 调用模块上的 create 钩子。

    • 处理 children 子节点数组:

    • 调用 vnode 上的 create 钩子。并将 vnode 上的 insert 钩子加入到  insert 钩子队列。

    • 如果 children 是数组,则递归调用 createElm() 创建子节点后,调用 appendChild 挂载到 vnode.elm 下。

    • 如果 children 不是数组但 vnode.text 存在,说明这个元素的内容是个文本,这个时候调用 createTextNode 创建文本节点并挂载到 vnode.elm 下。

    • 如果 vnode.sel === '!',这是 Snabbdom 用来删除原节点的方法,这样会新插入一个注释节点。因为在 createElm() 后会删除老节点,所以这样设置就可以达到卸载的目的。

    • 如果 vnode.sel 选择器定义是存在的:

    • 剩下的情况就是 vnode.sel 不存在,说明节点本身是文本,那就调用 createTextNode 创建文本节点并记录到 vnode.elm

  • 最后返回 vnode.elm

整个过程可以看出 createElm() 是根据 sel 选择器的不同设置来选择如何创建 DOM 节点。这里有个细节是补一下: patch() 中提到的 insert 钩子队列。需要这个 insert 钩子队列的原因是需要等到 DOM 真正被插入后才执行,而且也要等到所有子孙节点都插入完成,这样我们可以在 insert 中去计算元素的大小位置信息才是准确的。结合上面创建子节点的过程,createElm() 创建子节点是递归调用,所以队列会先记录子节点,再记录自身。这样在 patch() 的结尾执行这个队列时就可以保证这个顺序。

patchVnode()

接下来我们来看 Snabbdom 如何用 patchVnode() 来做 diff 的,这是虚拟 DOM 的核心。patchVnode() 的处理流程如下:

  • 首先执行 vnode 上 prepatch 钩子。

  • 如果 oldVnode 和 vnode 是同一个对象引用,则不处理直接返回。

  • 调用模块和 vnode 上的 update 钩子。

  • 如果没有定义 vnode.text,则处理 children 的几种情况:

    • 如果 oldVnode.childrenvnode.children 均存在并且不相同。则调用 updateChildren 去更新。

    • vnode.children 存在而 oldVnode.children 不存在。如果 oldVnode.text 存在则先清空,然后调用 addVnodes 去添加新的 vnode.children

    • vnode.children 不存在而 oldVnode.children 存在。调用 removeVnodes 移除 oldVnode.children

    • 如果 oldVnode.childrenvnode.children 均不存在。如果 oldVnode.text 存在则清空。

  • 如果有定义 vnode.text并且与 oldVnode.text 不同。如果 oldVnode.children 存在则调用 removeVnodes 清除。然后通过 textContent 来设置文本内容。

  • 最后执行 vnode 上的 postpatch 钩子。

从过程可以看出,diff 中对于自身节点的相关属性的改变比如 classstyle 之类的是依靠模块去更新的,这里不过多展开了大家有需要可以去看下模块相关的代码。diff 的主要核心处理是集中在 children 上,接下来康康 diff 处理 children 的几个相关函数。

addVnodes()

这个很简单,先调用 createElm() 创建,然后插入到对应的 parent 中。

removeVnodes()

移除的时候会先调用 destoryremove 钩子,这里重点讲讲这两个钩子的调用逻辑和区别。

  • destory,首先调用这个钩子。逻辑是先调用 vnode 对象上的这个钩子,再调用模块上的。然后对 vnode.children 也按照这个顺序递归调用这个钩子。

  • remove,这个 hook 只有在当前元素从它的父级中删除才会触发,被移除的元素中的子元素则不会触发,并且模块和 vnode 对象上的这个钩子都会调用,顺序是先调用模块上的再调用 vnode 上的。而且比较特殊的是等待所有的 remove 都会调用后,元素才会真正被移除,这样做可以实现一些延迟删除的需求。

以上可以看出这两个钩子调用逻辑不同的地方,特别是 remove 只在直接脱离父级的元素上才会被调用。

updateChildren()

updateChildren() 是用来处理子节点 diff 的,也是  Snabbdom  中比较复杂的一个函数。总的思想是对  oldChnewCh 各设置头、尾一共四个指针,这四个指针分别是 oldStartIdxoldEndIdxnewStartIdxnewEndIdx。然后在 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) 循环中对两个数组进行对比,找到相同的部分进行复用更新,并且每次比较处理最多移动一对指针。详细的遍历过程按以下顺序处理:

  • 如果这四个指针有任何一个指向的 vnode == null,则这个指针往中间移动,比如:start++ 或 end--,null 的产生在后面情况有说明。

  • 如果新旧开始节点相同,也就是 sameVnode(oldStartVnode, newStartVnode) 返回 true,则用 patchVnode() 执行 diff,并且两个开始节点都向中间前进一步。

  • 如果新旧结束节点相同,也采用 patchVnode() 处理,两个结束节点向中间后退一步。

  • 如果旧开始节点与新结束节点相同,先用 patchVnode() 处理更新。然后需要移动 oldStart 对应的 DOM 节点,移动的策略是移动到 oldEndVnode 对应 DOM 节点的下一个兄弟节点之前。为什么是这样移动呢?首先,oldStart 与 newEnd 相同,说明在当前循环处理中,老数组的开始节点是往右移动了;因为每次的处理都是首尾指针往中间移动,我们是把老数组更新成新的,这个时候 oldEnd 可能还没处理,但这个时候 oldStart 已确定在新数组的当前处理中是最后一个了,所以移动到 oldEnd 的下一个兄弟节点之前是合理的。移动完毕后,oldStart++,newEnd--,分别向各自的数组中间移动一步。

  • 如果旧结束节点与新开始节点相同,也是先用 patchVnode() 处理更新,然后把 oldEnd 对应的 DOM 节点移动 oldStartVnode 对应的  DOM 节点之前,移动理由同上一步一样。移动完毕后,oldEnd--,newStart++。

  • 如果以上情况都不是,则通过 newStartVnode 的 key 去找在 oldChildren 的下标 idx,根据下标是否存在有两种不同的处理逻辑:

    • 如果两个 vnode 的 sel 不同,也还是当做新创建的,通过 createElm() 创建新的 DOM,并插入到 oldStartVnode 对应的 DOM 之前。

    • 如果 sel 是相同的,则通过 patchVnode() 处理更新,并把 oldChildren 对应下标的 vnode 设置为 undefined,这也是前面双指针遍历中为什么会出现 == null 的原因。然后把更新完毕后的节点插入到 oldStartVnode 对应的 DOM 之前。

    • 如果下标不存在,说明 newStartVnode 是新创建的。通过 createElm() 创建新的 DOM,并插入到 oldStartVnode 对应的 DOM 之前。

    • 如果下标存在,也要分两种情况处理:

    • 以上操作完后,newStart++。

遍历结束后,还有两种情况要处理。一种是 oldCh 已经全部处理完成,而 newCh 中还有新的节点,需要对 newCh 剩下的每个都创建新的 DOM;另一种是 newCh 全部处理完成,而 oldCh 中还有旧的节点,需要将多余的节点移除。这两种情况的处理在 如下:

  function updateChildren(
    parentElm: Node,
    oldCh: VNode[],
    newCh: VNode[],
    insertedVnodeQueue: VNodeQueue
  ) { 
    // 双指针遍历过程。
    // ...
      
    // newCh 中还有新的节点需要创建。
    if (newStartIdx <= newEndIdx) {
      // 需要插入到最后一个处理好的 newEndIdx 之前。
      before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;
      addVnodes(
        parentElm,
        before,
        newCh,
        newStartIdx,
        newEndIdx,
        insertedVnodeQueue
      );
    }
      
    // oldCh 中还有旧的节点要移除。
    if (oldStartIdx <= oldEndIdx) {
      removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);
    }
  }

我们用一个实际例子来看一下 updateChildren() 的处理过程:

  • 初始状态如下,旧子节点数组为 [A, B, C],新节点数组为 [B, A, C, D]:

虚拟DOM是什么

  • 第一轮比较,开始和结束节点都不一样,于是看 newStartVnode 在旧节点中是否存在,找到了在 oldCh[1] 这个位置,那么先执行 patchVnode() 进行更新,然后把 oldCh[1] = undefined,并把 DOM 插入到 oldStartVnode 之前,newStartIdx 向后移动一步,处理完后状态如下:

虚拟DOM是什么

  • 第二轮比较,oldStartVnodenewStartVnode 相同,执行 patchVnode() 更新,oldStartIdxnewStartIdx 向中间移动,处理完后状态如下:

虚拟DOM是什么

  • 第三轮比较,oldStartVnode == nulloldStartIdx 向中间移动,状态更新如下:

虚拟DOM是什么

  • 第四轮比较,oldStartVnodenewStartVnode 相同,执行 patchVnode() 更新,oldStartIdxnewStartIdx 向中间移动,处理完后状态如下:

虚拟DOM是什么

  • 此时 oldStartIdx 大于 oldEndIdx,循环结束。此时 newCh 中还有没处理完的新节点,需要调用 addVnodes() 插入,最终状态如下:

虚拟DOM是什么

关于“虚拟DOM是什么”这篇文章的内容就介绍到这里,感谢各位的阅读!相信大家对“虚拟DOM是什么”知识都有一定的了解,大家如果还想学习更多知识,欢迎关注编程网行业资讯频道。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

虚拟DOM是什么

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Vue中简单的虚拟DOM是什么样

这些年写前端vue时经常碰到虚拟DOM这个单词,但都是听到就算了,左耳进右耳出,知道有这么个东西就完了,因为感觉也没有影响我实现公司的产品业务逻辑。今天在这里想花点时间探讨下虚拟DOM相关的知识,给自己补补课
2022-11-13

web前端:vue 的虚拟 DOM 有什么好处?

编程学习网:DocumentObjectModel的历史可以追溯至1990年代后期微软与Netscape的浏览器大战,双方为了在Javascript与JScript一决生死,于是大规模的赋予浏览器强大的功能。微软在网页技术上加入了不少专属事物,既有VBScript、ActiveX、以及微软自家的Dhtml格式等,使不少网页使用非微软平台及浏览器无法正常显示。DOM即是当时蕴酿出来的杰作。
web前端:vue 的虚拟 DOM 有什么好处?
2024-04-23

什么是虚拟机?

这是终端用户需要知道的所有关于虚拟机的信息,包括它的工作方式及其必要性。
虚拟机2024-11-30

虚拟DOM如何进化为真实DOM

Vue和React的Render函数中都涉及到了Virtual DOM的概念,Virtual DOM也是性能优化上的重要一环,同时突破了直接操作真实DOM的瓶颈,本文带着以下几个问题来阐述Virtual DOM。

什么是虚拟化?

近年来,“虚拟化”这个概念可谓炙手可热。对虚拟化最常见的解释是:把一台服务器,虚拟成相互隔离的多台“虚拟服务器”,也叫“虚拟机”。

什么是虚拟主机?为什么需要虚拟主机?

所谓虚拟主机,是指一台物理服务器上运行的多个虚拟主机实例。在这种情况下,每个虚拟主机被视为独立的服务器,它们共享服务器的资源,例如处理器、内存、存储等。虚拟主机通过利用服务器资源的共享,允许多个网站共存于同一台服务器上,每个网站享有一定数量的资源。
什么是虚拟主机?为什么需要虚拟主机?
2024-01-24

KVM 虚拟化是什么?

KVM虚拟化是一种基于内核的虚拟化解决方案,允许在单台物理服务器上运行多个独立的虚拟机。它利用Linux内核的虚拟化扩展,在硬件上创建和管理虚拟机环境。KVM具有高性能、隔离性、安全性、可扩展性和开源等优点,广泛应用于服务器整合、测试和开发、灾难恢复和云计算等场景。通过滿足相应的先决条件(硬件兼容性、Linux內核版本、虛擬化工具),可以部署和管理KVM虛擬化,並通過適當的配置,為組織和應用程序提供高效、安全的虛擬化解決方案。
KVM 虚拟化是什么?
2024-04-02

什么是虚拟现实?

虚拟现实(VR)是一种模拟技术,通过头显和计算机图形创建沉浸式虚拟世界。用户佩戴头显,遮蔽外部视野,呈现计算机图像,并根据头部运动调整,营造身处虚拟环境的错觉。VR主要由头显、跟踪系统、计算机和控制器组成。其类型包括内向型、外向型和基于定位型。VR应用广泛,包括游戏、教育、医疗、旅游和工业设计,提供沉浸感、交互性、视觉保真度和协作潜力。但它也面临晕动病、硬件成本、内容可用性和社交孤立等挑战。未来随着技术发展,VR预计将在各领域发挥更重要作用。
什么是虚拟现实?
2024-04-02

什么是Java虚拟机

这篇文章主要介绍“什么是Java虚拟机”,在日常操作中,相信很多人在什么是Java虚拟机问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”什么是Java虚拟机”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!一、
2023-06-03

什么是虚拟主机?为什么需要虚拟主机?

什么是虚拟主机?为什么需要虚拟主机?在当今数码化和互联网的时代,网站扮演着日益重要的角色,不论是企业还是个人,拥有一个专属的在线存在变得至关重要。虚拟主机成为很多企业和个人建站的优选方案。下面介绍下什么是虚拟主机和为什么虚拟主机受到众多用户的青睐。
什么是虚拟主机?为什么需要虚拟主机?
2024-01-24

什么是半虚拟化?

半虚拟化是一种虚拟化技术,介于完全虚拟化和操作系统级虚拟化之间。它不需要修改访客操作系统,但仍提供与完全虚拟化类似的隔离和资源管理。半虚拟化利用称为“hypercall”的软件接口在访客操作系统与虚拟机管理程序之间进行通信,从而访问底层硬件。与完全虚拟化相比,半虚拟化开销更低,更容易部署和管理。与操作系统级虚拟化相比,它提供更强的隔离和资源管理。半虚拟化广泛用于数据中心和云计算环境中,常见平台包括Xen、KVM和Hyper-V。
什么是半虚拟化?
2024-04-02

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录