vue.js diff算法原理详细解析

目录
  • diff算法的概念
  • 虚拟Dom
  • h函数
  • diff对比规则
    • patch
    • patchVnode
    • updateChildren
  • 总结

diff算法的概念

diff算法可以看作是一种对比算法,对比的对象是新旧虚拟Dom。顾名思义,diff算法可以找到新旧虚拟Dom之间的差异,但diff算法中其实并不是只有对比虚拟Dom,还有根据对比后的结果更新真实Dom

虚拟Dom

上面的概念我们提到了虚拟Dom,相信大家对这个名词并不陌生,下面为大家解释一下虚拟Dom的概念,以及diff算法中为什么要对比虚拟Dom,而不是直接去操作真实Dom。

虚拟Dom,其实很简单,就是一个用来描述真实Dom的对象

它有六个属性,sel表示当前节点标签名,data内是节点的属性,children表示当前节点的其他子标签节点,elm表示当前虚拟节点对应的真实节点(这里暂时没有),key即为当前节点的key,text表示当前节点下的文本,结构类似这样。

let vnode = {
    sel: 'ul',
    data: {},
    children: [
        {
            sel: 'li', data: { class: 'item' }, text: 'son1'
        },
        {
            sel: 'li', data: { class: 'item' }, text: 'son2'
        },    
    ],
    elm: undefined,
    key: undefined,
    text: undefined
}

那么虚拟Dom有什么用呢。我们其实可以把虚拟Dom理解成对应真实Dom的一种状态。当真实Dom发生变化后,虚拟Dom可以为我们提供这个真实Dom变化之前和变化之后的状态,我们通过对比这两个状态,即可得出真实Dom真正需要更新的部分,即可实现最小量更新。在一些比较复杂的Dom变化场景中,通过对比虚拟Dom后更新真实Dom会比直接更新真实Dom的效率高,这也就是虚拟Dom和diff算法真正存在的意义。

h函数

在介绍diff算法原理之前还需要简单让大家了解一下h函数,因为我们要靠它为我们生成虚拟Dom。这个h函数大家应该也比较熟悉,就是render函数里面传入的那个h函数

h函数可以接受多种类型的参数,但其实它内部只干了一件事,就是执行vnode函数。根据传入h函数的参数来决定执行vnode函数时传入的参数。那么vnode函数又是干什么的呢?vnode函数其实也只干了一件事,就是把传入h函数的参数转化为一个对象,即虚拟Dom。

// vnode.js
export default function (sel, data, children, text, elm) {
    const key = data.key
    return {sel, data, children, text, elm, key}
}

执行h函数后,内部会通过vnode函数生成虚拟Dom,h函数把这个虚拟在return出去。

diff对比规则

明确了h函数是干什么的,我们可以简单用h函数生成两个不同的虚拟节点,我们将通过一个简易版的diff算法代码介绍diff对比的具体流程。

// 第一个参数是sel 第二个参数是data 第三个参数是children
const myVnode1 = h("h1", {}, [
  h("p", {key: "a"}, "a"),
  h("p", {key: "b"}, "b"),
]);
const myVnode2 = h("h1", {}, [
  h("p", {key: "c"}, "c"),
  h("p", {key: "d"}, "d"),
]);

patch

比较的第一步就是执行patch,它相当于对比的入口。既然是对比两个虚拟Dom,那么就将两个虚拟Dom作为参数传入patch中。patch的主要作用是对比两个虚拟Dom的根节点,并根据对比结果操作真实Dom。

patch函数的核心代码如下,注意注释。

// patch.js
import vnode from "./vnode"
import patchDetails from "./patchVnode"
import createEle from "./createEle"
/**
 * @description 用来对比两个虚拟dom的根节点,并根据对比结果操作真实Dom
 * @param {*} oldVnode
 * @param {*} newVnode
 */
export function patch(oldVnode, newVnode) {
  // 1.判断oldVnode是否为虚拟节点,不是的话转化为虚拟节点
  if(!oldVnode.sel) {
    // 转化为虚拟节点
    oldVnode = vnode(oldVnode.tagName.toLowerCase(), {}, [], undefined, oldVnode)
  }
  // 2.判断oldVnode和newVnode是否为同一个节点
  if(oldVnode.key == newVnode.key && oldVnode.sel == newVnode.sel) {
    console.log('是同一个节点')
    // 比较子节点
    patchDetails(oldVnode, newVnode)
  }else {
    console.log('不是同一个节点')
    // 插入newVnode
    const newNode = createEle(newVnode) // 插入之前需要先将newVnode转化为dom
    oldVnode.elm.parentNode.insertBefore(newNode, oldVnode.elm) // 插入操作
    // 删除oldVnode
    oldVnode.elm.parentNode.removeChild(oldVnode.elm)
  }
}
// createEle.js
/**
 * @description 根据传入的虚拟Dom生成真实Dom
 * @param {*} vnode
 * @returns real node
 */
export default function createEle (vnode) {
  const realNode = document.createElement(vnode.sel)
​
  // 子节点转换
  if(vnode.text && (vnode.children == undefined || (vnode.children && vnode.children.length == 0)) ) {
    // 子节点只含有文本
    realNode.innerText = vnode.text  
  }else if(Array.isArray(vnode.children) && vnode.children.length > 0) {
    // 子节点为其他虚拟节点 递归添加node
    for(let i = 0; i < vnode.children.length; i++) {
      const childNode = createEle(vnode.children[i])
      realNode.appendChild(childNode)
    }
  }
  // 补充vnode的elm属性
  vnode.elm = realNode
  return vnode.elm
}

patchVnode

patchVnode用来比较两个虚拟节点的子节点并更新其子节点对应的真实Dom节点

// patchVnode.js
​
import updateChildren from "./updateChildren"
import createEle from "./createEle"
​
/**
 * @description 比较两个虚拟节点的子节点(children or text) 并更新其子节点对应的真实dom节点
 * @param {*} oldVnode
 * @param {*} newVnode
 * @returns
 */
export function patchDetails(oldVnode, newVnode) {
  // 判断oldVnode和newVnode是否为同一个对象, 是的话直接不用比了
  if(oldVnode == newVnode) return
​
  // 默认newVnode和oldVnode只有text和children其中之一,真实的源码这里的情况会更多一些,不过大同小异。
​
  if(hasText(newVnode)) {
    // newVnode有text但没有children
​
    /**
     *  newVnode.text !== oldVnode.text 直接囊括了两种情况
     *  1.oldVnode有text无children 但是text和newVnode的text内容不同
     *  2.oldVnode无text有children 此时oldVnode.text为undefined
     *  两种情况都可以通过innerText属性直接完成dom更新
     *  情况1直接更新text 情况2相当于去掉了children后加了新的text
     */
    if(newVnode.text !== oldVnode.text) {
      oldVnode.elm.innerText = newVnode.text
    }
  }else if(hasChildren(newVnode)) {
    // newVnode有children但是没有text
    
    if(hasText(oldVnode)) {
      // oldVnode有text但是没有children
      
      oldVnode.elm.innerText = '' // 删除oldVnode的text
      // 添加newVnode的children
      for(let i = 0; i < newVnode.children.length; i++) {
        oldVnode.elm.appendChild(createEle(newVnode.children[i]))
      }
    }else if(hasChildren(oldVnode)) {
      // oldVnode有children但是没有text
​
      // 对比两个节点的children 并更新对应的真实dom节点
      updateChildren(oldVnode.children, newVnode.children, oldVnode.elm)
    }
  }
}
// 有children没有text
function hasChildren(node) {
  return !node.text && (node.children && node.children.length > 0)
}
// 有text没有children
function hasText(node) {
  return node.text && (node.children == undefined || (node.children && node.children.length == 0))
} 

updateChildren

该方法是diff算法中最复杂的方法(大的要来了)。对应上面patchVnodeoldVnodenewVnode都有children的情况。

首先我们需要介绍一下这里的对比规则。

对比过程中会引入四个指针,分别指向oldVnode子节点列表中的第一个节点和最后一个节点(后面我们简称为旧前旧后)以及指向newVnode子节点列表中的第一个节点和最后一个节点(后面我们简称为新前新后

对比时,每一次对比按照以下顺序进行命中查找

  • 旧前与新前节点对比(1)
  • 旧后与新后节点对比(2)
  • 旧前与新后节点对比(3)
  • 旧后与新前节点对比(4)

上述四种情况,如果某一种情况两个指针对应的虚拟Dom相同,那么我们称之为命中。命中后就不会接着查找了,指针会移动,(还有可能会操作真实Dom,3或者4命中时会操作真实Dom移动节点)之后开始下一次对比。如果都没有命中,则去oldVnode子节点列表循环查找当前新前指针所指向的节点,如果查到了,那么操作真实Dom移动节点,没查到则新增真实Dom节点插入。

这种模式的对比会一直进行,直到满足了终止条件。即旧前指针移动到了旧后指针的后面或者新前指针移动到了新后指针的后面,我们可以理解为旧子节点先处理完毕新子节点处理完毕。那么我们可以预想到新旧子节点中总会有其一先处理完,对比结束后,我们会根据没有处理完子节点的那一对前后指针决定是要插入真实Dom还是删除真实Dom。

  • 如果旧子节点先处理完了,新子节点有剩余,说明有要新增的节点。将根据最终新前新后之间的虚拟节点执行插入操作
  • 如果新子节点先处理完了,旧子节点有剩余,说明有要删除的节点。将根据最终旧前旧后之间的虚拟节点执行删除操作

下面将呈现代码,注意注释:

// updateChildren.js
import patchDetails from "./patchVnode"
import createEle from "./createEle";
/**
 * @description 对比子节点列表并更新真实Dom
 * @param {*} oldCh 旧虚拟Dom子节点列表
 * @param {*} newCh 新虚拟Dom子节点列表
 * @param {*} parent 新旧虚拟节点对应的真实Dom
 * @returns
 */
export default function updateChildren(oldCh, newCh, parent) {
  // 定义四个指针 旧前 旧后 新前 新后 (四个指针两两一对,每一对前后指针所指向的节点以及其之间的节点为未处理的子节点)
  let oldStartIndex = 0;
  let oldEndIndex = oldCh.length - 1;
  let newStartIndex = 0;
  let newEndIndex = newCh.length - 1;
​
  // 四个指针对应的节点
  let oldStartNode = oldCh[oldStartIndex];
  let oldEndNode = oldCh[oldEndIndex];
  let newStartNode = newCh[newStartIndex];
  let newEndNode = newCh[newEndIndex];
​
  // oldCh中每个子节点 key 与 index的哈希表 用于四种对比规则都不匹配的情况下在oldCh中寻找节点
  const keyMap = new Map();
​
  /**
   * 开始遍历两个children数组进行细节对比
   * 对比规则:旧前-新前 旧后-新后 旧前-新后 旧后-新前
   * 对比之后指针进行移动
   * 直到指针不满足以下条件 意味着有一对前后指针之间再无未处理的子节点 则停止对比 直接操作DOM
   */
  while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
    // 这四种情况是为了让指针在移动的过程中跳过空节点
    if (oldStartNode == undefined) {
      oldStartNode = oldCh[++oldStartIndex];
    } else if (oldEndNode == undefined) {
      oldEndNode = oldCh[--oldEndIndex];
    } else if (newStartNode == undefined) {
      newStartNode = newCh[++newStartIndex];
    } else if (newEndNode == undefined) {
      newEndNode = newCh[--newEndIndex];
    } else if (isSame(oldStartNode, newStartNode)) {
      console.log("method1");
      // 旧前-新前是同一个虚拟节点
​
      // 两个子节点再对比他们的子节点并更新dom (递归切入点)
      patchDetails(oldStartNode, newStartNode);
      // 指针移动
      oldStartNode = oldCh[++oldStartIndex];
      newStartNode = newCh[++newStartIndex];
    } else if (isSame(oldEndNode, newEndNode)) {
      console.log("method2");
      // 旧后-新后是同一个虚拟节点
​
      // 两个子节点再对比他们的子节点并更新dom (递归切入点)
      patchDetails(oldEndNode, newEndNode);
      // 指针移动
      oldEndNode = oldCh[--oldEndIndex];
      newEndNode = newCh[--newEndIndex];
    } else if (isSame(oldStartNode, newEndNode)) {
      console.log("method3");
      // 旧前-新后是同一个虚拟节点
​
      // 两个子节点再对比他们的子节点并更新dom (递归切入点)
      patchDetails(oldStartNode, newEndNode);
​
      /**
       *  这一步多一个移动(真实)节点的操作
       *  需要把当前指针所指向的子节点 移动到 oldEndIndex所对应真实节点之后(也就是未处理真实节点的尾部)
       *  注意:这一步是在操作真实节点
       */
      parent.insertBefore(oldStartNode.elm, oldEndNode.elm.nextSibling);
​
      // 指针移动
      oldStartNode = oldCh[++oldStartIndex];
      newEndNode = newCh[--newEndIndex];
    } else if (isSame(oldEndNode, newStartNode)) {
      console.log("method4");
      // 旧后-新前 是同一个虚拟节点
​
      // 两个子节点再对比他们的子节点并更新dom (递归切入点)
      patchDetails(oldEndNode, newStartNode);
      /**
       *  这一步多一个移动(真实)节点的操作
       *  与method3不同在移动位置
       *  需要把当前指针所指向的子节点 移动到 oldStartIndex所对应真实节点之前(也就是未处理真实节点的顶部)
       *  注意:这一步是在操作真实节点
       */
      parent.insertBefore(oldEndNode.elm, oldCh[oldStartIndex].elm);
​
      // 指针移动
      oldEndNode = oldCh[--oldEndIndex];
      newStartNode = newCh[++newStartIndex];
    } else {
      console.log("does not match");
      // 四种规则都不匹配
​
      // 生成keyMap
      if (keyMap.size == 0) {
        for (let i = oldStartIndex; i <= oldEndIndex; i++) {
          if (oldCh[i].key) keyMap.set(oldCh[i].key, i);
        }
      }
​
      // 在oldCh中搜索当前newStartIndex所指向的节点
      if (keyMap.has(newStartNode.key)) {
        // 搜索到了
​
        // 先获取oldCh中该虚拟节点
        const oldMoveNode = oldCh[keyMap.get(newStartNode.key)];
        // 两个子节点再对比他们的子节点并更新dom (递归切入点)
        patchDetails(oldMoveNode, newStartNode);
​
        // 移动这个节点(移动的是真实节点)
        parent.insertBefore(oldMoveNode.elm, oldStartNode.elm);
​
        // 该虚拟节点设置为undefined(还记得最开始的四个条件吗,因为这里会将子节点制空,所以加了那四个条件)
        oldCh[keyMap.get(newStartNode.key)] = undefined;
          
      } else {
        // 没搜索到 直接插入
        parent.insertBefore(createEle(newStartNode), oldStartNode.elm);
      }
​
      // 指针移动
      newStartNode = newCh[++newStartIndex];
    }
  }
​
  /**
   * 插入和删除节点
   * while结束后 有一对前后指针之间仍然有未处理的子节点,那么就会进行插入或者删除操作
   * oldCh的双指针中有未处理的子节点,进行删除操作
   * newCh的双指针中有未处理的子节点,进行插入操作
   */
  if (oldStartIndex <= oldEndIndex) {
    // 删除
    for (let i = oldStartIndex; i <= oldEndIndex; i++) {
      // 加判断是因为oldCh[i]有可能为undefined
      if(oldCh[i]) parent.removeChild(oldCh[i].elm);
    }
  } else if (newStartIndex <= newEndIndex) {
    /**
     * 插入
     * 这里需要注意的点是从哪里插入,也就是appendChild的第二个参数
     * 应该从oldStartIndex对应的位置插入
     */
    for (let i = newStartIndex; i <= newEndIndex; i++) {
      // oldCh[oldStartIndex]存在是从头部插入
      parent.insertBefore(createEle(newCh[i]), oldCh[oldStartIndex] ? oldCh[oldStartIndex].elm : undefined);
    }
  }
}
​
// 判断两个虚拟节点是否为同一个虚拟节点
function isSame(a, b) {
  return a.sel == b.sel && a.key == b.key;
}

这里的逻辑稍微比较复杂,需要大家多理几遍,必要的话,自己手画一张图自己移动一下指针。着重需要注意的地方是操作真实Dom时,插入、移动节点应该将节点从哪里插入或者移动到哪里,其实基本插入到oldStartIndex对应的真实Dom的前面,除了第三种命中后的移动节点操作,是移动到oldEndIndex所对应真实节点之后

总结

由于diff算法对比的是虚拟Dom,而虚拟Dom是呈树状的,所以我们可以发现,diff算法中充满了递归。总结起来,其实diff算法就是一个 patch —> patchVnode —> updateChildren —> patchVnode —> updateChildren —> patchVnode这样的一个循环递归的过程。

这里再提一嘴key,我们面试中经常会被问到vue中key的作用。根据上面我们分析的,key的主要作用其实就是对比两个虚拟节点时,判断其是否为相同节点。加了key以后,我们可以更为明确的判断两个节点是否为同一个虚拟节点,是的话判断子节点是否有变更(有变更更新真实Dom),不是的话继续比。如果不加key的话,如果两个不同节点的标签名恰好相同,那么就会被判定为同一个节点(key都为undefined),结果一对比这两个节点的子节点发现不一样,这样会凭空增加很多对真实Dom的操作,从而导致页面更频繁地重绘和回流。

所以我认为合理利用key可以有效减少真实Dom的变动,从而减少页面重绘和回流的频率,进而提高页面更新的效率。

到此这篇关于vue.js diff算法原理详细解析的文章就介绍到这了,更多相关vue.js diff算法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue.js项目打包上线全流程

    目录 vue.js项目打包上线 这里我简单的列出这个过程 vue.js打包之后遇到的坑 打包之后没有被渲染出来 路由(router)mode:'history',导致页面不能渲染问题 vue.js项目打包上线 最近一直坚持每个月写一个小的vue.js 开发的项目,最后开发完成后想到很久之前给别人回答的一个问题:vue的项目如何上线,当时有千奇百怪的回答,我在想,这些人都是肿了么,vue的官方都说了,这个框架只是做了view这一层,所以并不是把这些开发完的东西直接拷贝到服务器上,而且需要打包为静态

  • Vue.js实现watch属性的示例详解

    目录 1.写在前面 2.watch的实现原理 3.立即执行的watch与回调执行时机 立即执行的回调函数 回调函数的执行时机 4.过期的副作用函数和cleanup 5.写在最后 1.写在前面 在上篇文章中,我们讨论了compted的实现原理,就是利用effect和options参数进行封装.同样的,watch也是基于此进行封装的,当然watch还可以传递第三参数去清理过期的副作用函数.不仅可以利用副作用函数的调度性,去实现回调函数的立即执行,也可以控制回调函数的执行时机. 2.watch的实现原

  • vue.js项目使用原生js实现移动端的轮播图

    目录 前言 一.了解原生js移动端的事件 二.轮播图实战 第一部分:template模板 第一部分解读: 第二部分:script标签内代码 第二部分解读: 第三部分:css样式部分 三.效果图 结束语 前言 今天我在vue.js项目实战开发过程中遇到了实现轮播图效果的问题,因为不想因为一个轮播图而引用整个jquery,而且我还发现自己根本就不清楚移动端的一些事件,所以我就进行了一些资料查找,并最终解决了这个问题,接下来跟大家分享一下我的解决问题的过程. 一.了解原生js移动端的事件 原生js移动

  • vue js中实现图片压缩封装方法

    全局main.js引入: // 引入imgUpload方法 import * as imgUpload from "./utils/imgUpload" //外部使用 Vue.prototype.$imgUpload = imgUpload 新建imgUpload.js: const dataURLtoFile = (dataurl, filename) => { // 将base64转换为file文件 let arr = dataurl.split(',') let mime

  • vue.js中修饰符.stop的用法解析

    目录 修饰符.stop的用法 用法如下 下面是全部的代码 stop事件修饰符 stop事件修饰符具体介绍 修饰符.stop的用法 .stop修饰符是用来阻止冒泡事件的发生的. 用法如下 <a v-on:click.stop="doThis"></a> 下面是全部的代码 <!DOCTYPE html> <html lang="en"> <head>     <meta charset="UTF

  • vue.js实现动态面包屑

    最近在学习vue,正好手上有个项目还是用jquery写的,就自己尝试将这个项目的前端用vue实现,途中遇到了动态面包屑的问题,特此记录一下,如有不对的地方,欢迎指正. 需求描述: 点击左侧的导航,跳转到不同的页面,并且根据导航的路径,动态生成面包屑.面包屑的数据均来自于左侧的导航. 思路: 1.面包屑作为一个单独的组件,在页面中引入.页面的结构如下: <template>     <div class="page-center">         <top

  • Vue.js中v-model指令的用法介绍

    一.v-model指令 v-model 用来获取表单元素的值.对应input输入框获取的是输入的值,单选按钮.复选框.下拉框获取的是选择的状态. 使用v-model可以在表单控件或者组件上创建双向绑定. 代码示例如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" con

  • vue.js diff算法原理详细解析

    目录 diff算法的概念 虚拟Dom h函数 diff对比规则 patch patchVnode updateChildren 总结 diff算法的概念 diff算法可以看作是一种对比算法,对比的对象是新旧虚拟Dom.顾名思义,diff算法可以找到新旧虚拟Dom之间的差异,但diff算法中其实并不是只有对比虚拟Dom,还有根据对比后的结果更新真实Dom. 虚拟Dom 上面的概念我们提到了虚拟Dom,相信大家对这个名词并不陌生,下面为大家解释一下虚拟Dom的概念,以及diff算法中为什么要对比虚拟

  • React diff算法原理详细分析

    目录 抛砖引玉 传统diff算法 React diff原理 tree diff component diff element diff 应用实践 页面指定区域刷新 更加方便地监听props改变 react-router中Link问题 结语 抛砖引玉 React通过引入Virtual DOM的概念,极大地避免无效的Dom操作,已使我们的页面的构建效率提到了极大的提升.但是如何高效地通过对比新旧Virtual DOM来找出真正的Dom变化之处同样也决定着页面的性能,React用其特殊的diff算法解

  • 详解vue的diff算法原理

    我的目标是写一个非常详细的关于diff的干货,所以本文有点长.也会用到大量的图片以及代码举例,目的让看这篇文章的朋友一定弄明白diff的边边角角. 先来了解几个点... 1. 当数据发生变化时,vue是怎么更新节点的? 要知道渲染真实DOM的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排,有没有可能我们只更新我们修改的那一小块dom而不要更新整个dom呢?diff算法能够帮助我们. 我们先根据真实DOM生成一颗 virtual DOM ,当

  • Vue的diff算法原理你真的了解吗

    目录 思维导图 0.从常见问题引入 1.生成虚拟dom 1.h方法实现 2.render方法实现 3.再次渲染 2.diff算法 1.对常见的dom做优化 情况1:末尾追加一个元素(头和头相同) 情况2:队首添加一个节点(尾和尾) 情况3:翻转类型(头和尾) 情况4:暴力比对复用 对于key的探讨 1.为什么不能没有key 2.为什么key不能是index 3.diff的遍历方式 总结 思维导图 0. 从常见问题引入 虚拟dom是什么? 如何创建虚拟dom? 虚拟dom如何渲染成真是dom? 虚

  • react diff算法源码解析

    React中Diff算法又称为调和算法,对应函数名为reconcileChildren,它的主要作用是标记更新过程中那些元素发生了变化,这些变化包括新增.移动.删除.过程发生在beginWork阶段,只有非初次渲染才会Diff. 以前看过一些文章将Diff算法表述为两颗Fiber树的比较,这是不正确的,实际的Diff过程是一组现有的Fiber节点和新的由JSX生成的ReactElement的比较,然后生成新的Fiber节点的过程,这个过程中也会尝试复用现有Fiber节点. 节点Diff又分为两种

  • Vue子组件与父组件详细解析

    目录 一.父组件和子组件 二.模板分离写法 1.template标签 2.text/x-template 三.父子组件通信-父传子 1.Prop 类型 四.父子组件通信子传父 1.vm.$emit( eventName, [-args] ) 五.父子组件通信-结合双向绑定案例 1.基本模板代码 2.增加双向绑定 3.反向绑定 六.组件访问父访问子 一.父组件和子组件 我们经常分不清什么是父组件,什么是子组件.现在来简单总结下:我们将某段代码封装成一个组件,而这个组件又在另一个组件中引入,而引入该

  • Java 负载均衡算法作用详细解析

    目录 前言 轮询算法 随机算法 加权随机算法 加权轮询算法 源地址hash算法 最小请求数算法 前言 负载均衡在Java领域中有着广泛深入的应用,不管是大名鼎鼎的nginx,还是微服务治理组件如dubbo,feign等,负载均衡的算法在其中都有着实际的使用 负载均衡的核心思想在于其底层的算法思想,比如大家熟知的算法有 轮询,随机,最小连接,加权轮询等,在现实中不管怎么配置,都离不开其算法的核心原理,下面将结合实际代码对常用的负载均衡算法做一些全面的总结. 轮询算法 轮询即排好队,一个接一个的轮着

  • Vue高级特性概念原理详细分析

    目录 1. 自定义v-model 2. $nextTick 3. slot 插槽 4. Vue 动态组件 5. Vue 异步组件 6. 使用 keep-alive 缓存组件 7. mixin 混入 1. 自定义v-model Vue中的自定义v-model指的是在自定义组件中使用v-model语法糖来实现双向绑定.在Vue中,通过v-model指令可以将表单元素的值与组件实例的数据进行双向绑定.但是对于自定义组件,如果要实现v-model的双向绑定,就需要自定义v-model的实现方式. 自定义

  • golang切片原理详细解析

    目录 切片的解析 切片的初始化 字面量初始化 make初始化 切片的截取 切片的复制 切片的扩容 总结 切片的解析 当我们的代码敲下[]时,便会被go编译器解析为抽象语法树上的切片节点, 被初始化为切片表达式SliceType: // go/src/cmd/compile/internal/syntax/parser.go // TypeSpec = identifier [ TypeParams ] [ "=" ] Type . func (p *parser) typeDecl(g

  • Golang Mutex 原理详细解析

    目录 前言 Lock 单协程加锁 加锁被阻塞 Unlock 无协程阻塞下的解锁 解锁并唤醒协程 自旋 什么是自旋 自旋条件 自旋的优势 自旋的问题 Mutex 的模式 Normal 模式 Starving 模式 Woken 状态 前言 互斥锁是在并发程序中对共享资源进行访问控制的主要手段.对此 Go 语言提供了简单易用的 Mutex.Mutex 和 Goroutine 合作紧密,概念容易混淆,一定注意要区分各自的概念. Mutex 是一个结构体,对外提供 Lock()和Unlock()两个方法,

随机推荐