virtual-dom的历史

react最早研究virtual-dom,后来react火了之后,大家纷纷研究react的高性能实现,出现了2个流派,一是研究react算法的算法派,(virtual-dom.js,cito.js,kivi.js,snabbdom.js,inferno),二是研究react的形态,react的迷你版-preact, inferno, react-lite, dio, rax。

算法派virtual-dom最开始研究react的diff算法,它通过深度优先搜索与in-order tree来实现高效的diff。它与React后来公开出来的算法是很不一样。

然后是cito.js,它采用两端同时进行比较的算法,将diff速度拉高到几个层次。

紧随其后的是kivi.js,在cito.js的基出提出两项优化方案,使用key实现移动追踪及基于key的编辑长度矩离算法应用(算法复杂度 为O(n^2))。

但这样的diff算法太过复杂了,于是后来者snabbdom将kivi.js进行简化,去掉编辑长度矩离算法,调整两端比较算法。速度略有损失,但可读性大大提高。再之后,就是著名的vue2.0 把sanbbdom整个库整合掉了。

当然算法派的老大是inferno,它使用多种优化方案将性能提至最高,因此其作者便邀请进react core team,负责react的性能优化了。

司徒正美《去哪儿网迷你React的研发心得》 https://segmentfault.com/a/1190000011235844

优点

相比于频繁的手动去操作dom而带来性能问题,vdom很好的将dom做了一层映射关系,进而将在我们本需要直接进行dom的一系列操作,映射到了操作vdom。

这一系列操作都是单纯依赖js,使得服务端渲染ssr得到更好的解决方案。

通过操作vdom来提高直接操作的dom的效率和性能。

有人会问为啥运行js会比直接操作更快呢?

在我理解,react并没说virtual-dom比原生更快阿。得到dom diff最后还是要通过操作原生dom render到浏览器上,所以肯定是原生更快的,但是这里说的快是指的react的batchUpdate机制。

  • 大概理解就是每次setState并不会立即执行,它会整合多个setState一起进行操作。
  • 大概原理就是react给函数封装了一层Transaction,它可以让react在函数执行前后执行一个钩子,initialize(函数执行前)阶段下创建一个update queue,setState把状态推入update queue,close(函数执行后)阶段update queue被flush。

    vue的实现就比较简单,直接push进update queue,利用micro-task队列(异步操作),尽快的执行update queue flush。

snabbdom.js的实现(vue diff算法)

snabbdom 主要的接口有:

  • h(type, data, children),返回 Virtual DOM 树。
  • patch(oldVnode, newVnode),比较新旧 Virtual DOM 树并更新。

vnode

  1. const VNODE_TYPE = Symbol('virtual-node')
  2. function vnode(type, key, data, children, text, elm) {
  3. const element = {
  4. __type: VNODE_TYPE,
  5. type, key, data, children, text, elm
  6. }
  7. return element
  8. }
  9. function isVnode(vnode) {
  10. return vnode && vnode.__type === VNODE_TYPE
  11. }
  12. function isSameVnode(oldVnode, vnode) {
  13. return oldVnode.key === vnode.key && oldVnode.type === vnode.type
  14. }

利用__type是Symbol的特性来校验vnode。

生成了这样一个js对象

  1. {
  2. type, // String,DOM 节点的类型,如 'div'/'span'
  3. data, // Object,包括 props,style等等 DOM 节点的各种属性
  4. children // Array,子节点(子 vnode)
  5. }

生成dom树

  1. function h(type, config, ...children) {
  2. const props = {}
  3. // 省略用 config 填充 props 的过程
  4. return vnode(
  5. type,
  6. key,
  7. props,
  8. flattenArray(children).map(c => {
  9. return isPrimitive(c) ? vnode(undefined, undefined, undefined, undefined, c) : c
  10. })
  11. )
  12. }

dom diff-两端比较法

通常找到两棵任意的树之间最小修改的时间复杂度是 O(n^3),但是我们可以对dom做一些特别的假设:

如果oldVnode 和 vnode 不同(如 type 从 div 变到 p,或者 key 改变),意味着整个 vnode 被替换(因为我们通常不会去跨层移动 vnode ),所以我们没有必要去比较 vnode 的 子 vnode(children) 了。

基于这个假设,我们可以 按照层级分解 树,这大大简化了复杂度,大到接近 O(n) 的复杂度。

  1. function patchVnode(oldVnode, vnode, insertedVnodeQueue) {
  2. // 因为 vnode 和 oldVnode 是相同的 vnode,所以我们可以复用 oldVnode.elm。
  3. const elm = vnode.elm = oldVnode.elm
  4. let oldCh = oldVnode.children
  5. let ch = vnode.children
  6. // 如果 oldVnode 和 vnode 是完全相同,说明无需更新,直接返回。
  7. if (oldVnode === vnode) return
  8. // 调用 update hook
  9. if (vnode.data) {
  10. for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode)
  11. }
  12. // 如果 vnode.text 是 undefined
  13. if (vnode.text === undefined) {
  14. // 比较 old children 和 new children,并更新
  15. if (oldCh && ch) {
  16. if (oldCh !== ch) {
  17. // 核心逻辑(最复杂的地方):怎么比较新旧 children 并更新,对应上面
  18. // 的数组比较
  19. updateChildren(elm, oldCh, ch, insertedVnodeQueue)
  20. }
  21. }
  22. // 添加新 children
  23. else if (ch) {
  24. // 首先删除原来的 text
  25. if (oldVnode.text) api.setTextContent(elm, '')
  26. // 然后添加新 dom(对 ch 中每个 vnode 递归创建 dom 并插入到 elm)
  27. addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
  28. }
  29. // 相反地,如果原来有 children 而现在没有,那么我们要删除 children。
  30. else if (oldCh) {
  31. removeVnodes(elm, oldCh, 0, oldCh.length - 1);
  32. }
  33. // 最后,如果 oldVnode 有 text,删除。
  34. else if (oldVnode.text) {
  35. api.setTextContent(elm, '');
  36. }
  37. }
  38. // 否则 (vnode 有 text),只要 text 不等,更新 dom 的 text。
  39. else if (oldVnode.text !== vnode.text) {
  40. api.setTextContent(elm, vnode.text)
  41. }
  42. }
  43. function updateChildren(parentElm, oldCh, newCh, insertedVnodeQueue) {
  44. let oldStartIdx = 0, newStartIdx = 0
  45. let oldEndIdx = oldCh.length - 1
  46. let oldStartVnode = oldCh[0]
  47. let oldEndVnode = oldCh[oldEndIdx]
  48. let newEndIdx = newCh.length - 1
  49. let newStartVnode = newCh[0]
  50. let newEndVnode = newCh[newEndIdx]
  51. let oldKeyToIdx
  52. let idxInOld
  53. let elmToMove
  54. let before
  55. // 遍历 oldCh 和 newCh 来比较和更新
  56. while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
  57. // 1⃣️ 首先检查 4 种情况,保证 oldStart/oldEnd/newStart/newEnd
  58. // 这 4 个 vnode 非空,左侧的 vnode 为空就右移下标,右侧的 vnode 为空就左移 下标。
  59. if (oldStartVnode == null) {
  60. oldStartVnode = oldCh[++oldStartIdx]
  61. } else if (oldEndVnode == null) {
  62. oldEndVnode = oldCh[--oldEndIdx]
  63. } else if (newStartVnode == null) {
  64. newStartVnode = newCh[++newStartIdx]
  65. } else if (newEndVnode == null) {
  66. newEndVnode = newCh[--newEndIdx]
  67. }
  68. /**
  69. * 2⃣️ 然后 oldStartVnode/oldEndVnode/newStartVnode/newEndVnode 两两比较,
  70. * 对有相同 vnode 的 4 种情况执行对应的 patch 逻辑。
  71. * - 如果同 start 或同 end 的两个 vnode 是相同的(情况 1 和 2),
  72. * 说明不用移动实际 dom,直接更新 dom 属性/children 即可;
  73. * - 如果 start 和 end 两个 vnode 相同(情况 3 和 4),
  74. * 那说明发生了 vnode 的移动,同理我们也要移动 dom。
  75. */
  76. // 1. 如果 oldStartVnode 和 newStartVnode 相同(key相同),执行 patch
  77. else if (isSameVnode(oldStartVnode, newStartVnode)) {
  78. // 不需要移动 dom
  79. patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
  80. oldStartVnode = oldCh[++oldStartIdx]
  81. newStartVnode = newCh[++newStartIdx]
  82. }
  83. // 2. 如果 oldEndVnode 和 newEndVnode 相同,执行 patch
  84. else if (isSameVnode(oldEndVnode, newEndVnode)) {
  85. // 不需要移动 dom
  86. patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
  87. oldEndVnode = oldCh[--oldEndIdx]
  88. newEndVnode = newCh[--newEndIdx]
  89. }
  90. // 3. 如果 oldStartVnode 和 newEndVnode 相同,执行 patch
  91. else if (isSameVnode(oldStartVnode, newEndVnode)) {
  92. patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
  93. // 把获得更新后的 (oldStartVnode/newEndVnode) 的 dom 右移,移动到
  94. // oldEndVnode 对应的 dom 的右边。为什么这么右移?
  95. // (1)oldStartVnode 和 newEndVnode 相同,显然是 vnode 右移了。
  96. // (2)若 while 循环刚开始,那移到 oldEndVnode.elm 右边就是最右边,是合理的;
  97. // (3)若循环不是刚开始,因为比较过程是两头向中间,那么两头的 dom 的位置已经是
  98. // 合理的了,移动到 oldEndVnode.elm 右边是正确的位置;
  99. // (4)记住,oldVnode 和 vnode 是相同的才 patch,且 oldVnode 自己对应的 dom
  100. // 总是已经存在的,vnode 的 dom 是不存在的,直接复用 oldVnode 对应的 dom。
  101. api.insertBefore(parentElm, oldStartVnode.elm, api.nextSibling(oldEndVnode.elm))
  102. oldStartVnode = oldCh[++oldStartIdx]
  103. newEndVnode = newCh[--newEndIdx]
  104. }
  105. // 4. 如果 oldEndVnode 和 newStartVnode 相同,执行 patch
  106. else if (isSameVnode(oldEndVnode, newStartVnode)) {
  107. patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
  108. // 这里是左移更新后的 dom,原因参考上面的右移。
  109. api.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
  110. oldEndVnode = oldCh[--oldEndIdx]
  111. newStartVnode = newCh[++newStartIdx]
  112. }
  113. // 3⃣️ 最后一种情况:4 个 vnode 都不相同,那么我们就要
  114. // 1. 从 oldCh 数组建立 key --> index 的 map。
  115. // 2. 只处理 newStartVnode (简化逻辑,有循环我们最终还是会处理到所有 vnode),
  116. // 以它的 key 从上面的 map 里拿到 index;
  117. // 3. 如果 index 存在,那么说明有对应的 old vnode,patch 就好了;
  118. // 4. 如果 index 不存在,那么说明 newStartVnode 是全新的 vnode,直接
  119. // 创建对应的 dom 并插入。
  120. else {
  121. // 如果 oldKeyToIdx 不存在,创建 old children 中 vnode 的 key 到 index 的
  122. // 映射,方便我们之后通过 key 去拿下标。
  123. if (oldKeyToIdx === undefined) {
  124. oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
  125. }
  126. // 尝试通过 newStartVnode 的 key 去拿下标
  127. idxInOld = oldKeyToIdx[newStartVnode.key]
  128. // 下标不存在,说明 newStartVnode 是全新的 vnode。
  129. if (idxInOld == null) {
  130. // 那么为 newStartVnode 创建 dom 并插入到 oldStartVnode.elm 的前面。
  131. api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm)
  132. newStartVnode = newCh[++newStartIdx]
  133. }
  134. // 下标存在,说明 old children 中有相同 key 的 vnode,
  135. else {
  136. elmToMove = oldCh[idxInOld]
  137. // 如果 type 不同,没办法,只能创建新 dom;
  138. if (elmToMove.type !== newStartVnode.type) {
  139. api.insertBefore(parentElm, createElm(newStartVnode, insertedVnodeQueue), oldStartVnode.elm)
  140. }
  141. // type 相同(且key相同),那么说明是相同的 vnode,执行 patch。
  142. else {
  143. patchVnode(elmToMove, newStartVnode, insertedVnodeQueue)
  144. oldCh[idxInOld] = undefined
  145. api.insertBefore(parentElm, elmToMove.elm, oldStartVnode.elm)
  146. }
  147. newStartVnode = newCh[++newStartIdx]
  148. }
  149. }
  150. }
  151. // 上面的循环结束后(循环条件有两个),处理可能的未处理到的 vnode。
  152. // 如果是 new vnodes 里有未处理的(oldStartIdx > oldEndIdx
  153. // 说明 old vnodes 先处理完毕)
  154. if (oldStartIdx > oldEndIdx) {
  155. before = newCh[newEndIdx+1] == null ? null : newCh[newEndIdx+1].elm
  156. addVnodes(parentElm, before, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
  157. }
  158. // 相反,如果 old vnodes 有未处理的,删除 (为处理 vnodes 对应的) 多余的 dom。
  159. else if (newStartIdx > newEndIdx) {
  160. removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
  161. }
  162. }

首先,比较新老dom,如果都有,就进行diff,如果没有老的,直接插入新dom,如果没有新的有老dom,就删掉老dom。

主要逻辑是在updateChildren,进行新老dom的diff。

通过索引,遍历oldCh和newCh,这里新老两个队列分为5个情况去处理。

  1. 同 start不需要移动dom,直接更新 dom 属性/children
  2. 同 end 同上
  3. oldstart === newend,把老dom移到oldend的右边。
  4. oldend === oldstart, 老dom移到oldstart的左边。
  5. 如果4个都不同,从oldCh 数组建立 key --> index 的 map。取老dom的key,如果没有再重建。

例如:不带key的dom diff过程:



第一次遍历,发现b不等于oldstart也不等于oldend,新建b dom,插入最前面



第二次遍历,oldstart === newstart,不移动dom。



第三次遍历,newstart === oldend,就把end移到前面。



第四次遍历,newstart === oldend,就把end移到前面。



第五次遍历,发现没有f,新建f,移动到最后面。



第六次遍历,发现老的里面还有b,删掉。

带key的情况下,第一次遍历的发现4个都不相等,然后从oldch的map里面取对应的dom。这样就只需要新建一次f的dom。

render

render比较简单,根据 type 生成对应的 DOM,把 data 里定义的 各种属性设置到 DOM 上。

  1. function createElm(vnode, insertedVnodeQueue) {
  2. let data = vnode.data
  3. let i
  4. // 省略 hook 调用
  5. let children = vnode.children
  6. let type = vnode.type
  7. /// 根据 type 来分别生成 DOM
  8. // 处理 comment
  9. if (type === 'comment') {
  10. if (vnode.text == null) {
  11. vnode.text = ''
  12. }
  13. vnode.elm = api.createComment(vnode.text)
  14. }
  15. // 处理其它 type
  16. else if (type) {
  17. const elm = vnode.elm = data.ns
  18. ? api.createElementNS(data.ns, type)
  19. : api.createElement(type)
  20. // 调用 create hook
  21. for (let i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode)
  22. // 分别处理 children 和 text。
  23. // 这里隐含一个逻辑:vnode 的 children 和 text 不会/应该同时存在。
  24. if (isArray(children)) {
  25. // 递归 children,保证 vnode tree 中每个 vnode 都有自己对应的 dom;
  26. // 即构建 vnode tree 对应的 dom tree。
  27. children.forEach(ch => {
  28. ch && api.appendChild(elm, createElm(ch, insertedVnodeQueue))
  29. })
  30. }
  31. else if (isPrimitive(vnode.text)) {
  32. api.appendChild(elm, api.createTextNode(vnode.text))
  33. }
  34. // 调用 create hook;为 insert hook 填充 insertedVnodeQueue。
  35. i = vnode.data.hook
  36. if (i) {
  37. i.create && i.create(emptyNode, vnode)
  38. i.insert && insertedVnodeQueue.push(vnode)
  39. }
  40. }
  41. // 处理 text(text的 type 是空)
  42. else {
  43. vnode.elm = api.createTextNode(vnode.text)
  44. }
  45. return vnode.elm
  46. }

详见https://github.com/creeperyang/blog/issues/33

react16的diff算法

可以先看下react16的结构(https://www.cnblogs.com/dh-dh/p/9361555.html),react16架构已经全改,新架构是利用了两个fiber链表进行dom的diff。

因为改成了链表不再是树结构,就采用了简单的两端比较法。而且它也拿不到两个链表的尾标进行比较。

跟之前的react处理方式大概一样,都是给diff打tag,然后一块commit,这点跟vue有些不同,vue是找到diff直接处理。

react这种打tag的做法比较好的把dom diff跟dom render分开了,使得跨平台得到比较好的处理。

  1. const PLACEMENT = 1
  2. const DELETION = 2
  3. const UPDATE = 3
  4. function placeChild(currentFiber, newChild) {
  5. const type = newChild.type
  6. if (typeof newChild === 'string' || typeof newChild === 'number') {
  7. // 如果这个节点没有 type ,这个节点就可能是 number 或者 string
  8. return createFiber(tag.HostText, null, newChild, currentFiber, PLACEMENT)
  9. }
  10. if (typeof type === 'string') {
  11. // 原生节点
  12. return createFiber(tag.HOST_COMPONENT, newChild.type, newChild.props, currentFiber, PLACEMENT)
  13. }
  14. if (typeof type === 'function') {
  15. const _tag = type.prototype.isReactComponent ? tag.CLASS_COMPONENT : tag.FunctionalComponent
  16. return {
  17. type: newChild.type,
  18. tag: _tag,
  19. props: newChild.props,
  20. return: currentFiber,
  21. effectTag: PLACEMENT
  22. }
  23. }
  24. }
  25. function reconcileChildrenArray(currentFiber, newChildren) {
  26. // 对比节点,相同的标记更新
  27. // 不同的标记 替换
  28. // 多余的标记删除,并且记录下来
  29. const arrayfiyChildren = arrayfiy(newChildren)
  30. let index = 0
  31. let oldFiber = currentFiber.alternate ? currentFiber.alternate.child : null
  32. let newFiber = null
  33. while (index < arrayfiyChildren.length || oldFiber !== null) {
  34. const prevFiber = newFiber
  35. const newChild = arrayfiyChildren[index]
  36. const isSameFiber = oldFiber && newChild && newChild.type === oldFiber.type
  37. if (isSameFiber) {
  38. newFiber = {
  39. type: oldFiber.type,
  40. tag: oldFiber.tag,
  41. stateNode: oldFiber.stateNode,
  42. props: newChild.props,
  43. return: currentFiber,
  44. alternate: oldFiber,
  45. partialState: oldFiber.partialState,
  46. effectTag: UPDATE
  47. }
  48. }
  49. if (!isSameFiber && newChild) {
  50. newFiber = placeChild(currentFiber, newChild)
  51. }
  52. if (!isSameFiber && oldFiber) {
  53. // 这个情况的意思是新的节点比旧的节点少
  54. // 这时候,我们要将变更的 effect 放在本节点的 list 里
  55. oldFiber.effectTag = DELETION
  56. currentFiber.effects = currentFiber.effects || []
  57. currentFiber.effects.push(oldFiber)
  58. }
  59. if (oldFiber) {
  60. oldFiber = oldFiber.sibling || null
  61. }
  62. if (index === 0) {
  63. currentFiber.child = newFiber
  64. } else if (prevFiber && newChild) {
  65. prevFiber.sibling = newFiber
  66. }
  67. index++
  68. }
  69. return currentFiber.child
  70. }

大体过程就是遍历一下新链表,看一下新老链表的当前结点是不是同一个,如果是就打个update的标签,如果不同,直接替换当前fiber结构,打PLACEMENT标签。

如果没有新结点,老的存在,那就打个DELETION的tag。

可能会有一个问题,我没看到老fiber是从哪来的啊?

每个fiber会通过alternate连接另一个fiber,就是说我们操作的currentFiber是新fiber,currentFiber.alternate就是老fiber。

新fiber:newChild = arrayfiyChildren[index],老fiber用oldFiber.sibling遍历。

react有2个fiber 链表,一个做dom diff用,另一个做中断的备份用

virtual-dom的更多相关文章

  1. 窥探Vue.js 2.0 - Virtual DOM到底是个什么鬼?

    引言 你可能听说在Vue.js 2.0已经发布,并且在其中新添加如了一些新功能.其中一个功能就是"Virtual DOM". Virtual DOM是什么 在之前,React和Em ...

  2. 个人对于Virtual DOM的一些理解

    之前一直认为react的Virtual DOM操作会比传统的操作DOM要快,这其实是错误的,React 从来没有说过 "React 比原生操作 DOM 快".如果没有 Virtua ...

  3. 抛开react,如何理解virtual dom和immutability

    去年以来,React的出现为前端框架设计和编程模式吹来了一阵春风.很多概念,无论是原本已有的.还是由React首先提出的,都因为React的流行而倍受关注,成为大家研究和学习的热点.本篇分享主要就聚焦 ...

  4. 深度剖析:如何实现一个 Virtual DOM 算法

    本文转载自:https://github.com/livoras/blog/issues/13 目录: 1 前言 2 对前端应用状态管理思考 3 Virtual DOM 算法 4 算法实现 4.1 步 ...

  5. Virtual DOM 算法

    前端 virtual-dom react.js javascript 目录: 1 前言 2 对前端应用状态管理思考 3 Virtual DOM 算法 4 算法实现 4.1 步骤一:用JS对象模拟DOM ...

  6. React v16-alpha 从virtual dom 到 dom 源码简读

    一.物料准备 1.克隆react源码, github 地址:https://github.com/facebook/react.git 2.安装gulp 3.在react源码根目录下: $npm in ...

  7. React源码解析-Virtual DOM解析

    前言:最近一直在研究React,看了陈屹先生所著的深入React技术栈,以及自己使用了这么长时间.对React应该说有比较深的理解了,正好前阵子也把两本关于前端设计模式的书看完了,总感觉有一种知识错综 ...

  8. virtual dom的实践

    最近基于virtual dom 写了一个小框架-aoy. aoy是一个轻量级的mvvm框架,基于Virtual DOM.虽然现在看起来很单薄,但我做了完善的单元测试,可以放心使用.aoy的原理可以说和 ...

  9. 深度理解 Virtual DOM

    目录: 1 前言 2 技术发展史 3 Virtual DOM 算法 4 Virtual DOM 实现 5 Virtual DOM 树的差异(Diff算法) 6 结语 7 参考链接 1 前言 我会尽量把 ...

  10. vue的Virtual Dom实现- snabbdom解密

    vue在官方文档中提到与react的渲染性能对比中,因为其使用了snabbdom而有更优异的性能. JavaScript 开销直接与求算必要 DOM 操作的机制相关.尽管 Vue 和 React 都使 ...

随机推荐

  1. C# 用户控件之温度计

    本文以一个用户控件[User Control]实现温度计的小例子,简述用户控件的相关知识,以供学习分享使用,如有不足之处,还请指正. 概述 一般而言,用户控件[User Control],是在Visu ...

  2. win10系统关闭自动更新

    win10关闭自动更新 步骤①右键“此电脑”选择“管理”选项 步骤②(如下图所示): 步骤③:     步骤④: 好啦!这样就大功告成了!

  3. Linux 环境下 Git 安装与基本配置

    索引: 目录索引 参看代码 GitHub: git.txt 一.Linux (DeepinOS) 环境 1.安装 sudo apt-get update sudo apt-get install gi ...

  4. 定时删除所有文件夹下的_desktop.ini文件

    写个批处理,删除对应的文件,命名为DELDesktopIni.bat,存于D盘根目录 @echo off :delini for %%a in ( C: D: E: ) DO ( del /f/s/a ...

  5. SQL SERVER-查询爆破sa密码的主机

    drop table if exists #sql create table #sql ( Logdatae ), processinfo ), [text] varchar(max) ) go IN ...

  6. 如何让EasyUI的Tree或者ComboTree节点不显示图标?

    版本:jQuery EasyUI 1.3.2 通过测试,只需把节点的state属性设置为null即可使EasyUI的Tree或者ComboTree控件的节点不显示图标.

  7. elasticsearch系列八:ES 集群管理(集群规划、集群搭建、集群管理)

    一.集群规划 搭建一个集群我们需要考虑如下几个问题: 1. 我们需要多大规模的集群? 2. 集群中的节点角色如何分配? 3. 如何避免脑裂问题? 4. 索引应该设置多少个分片? 5. 分片应该设置几个 ...

  8. javascript基础之函数

    javascript的函数定义与python有很大的区别,的记住格式就好,下面请看代码 // 函数 // 简单定义 function func() { console.log('hello word' ...

  9. hmac_检验客户端是否合法

    老师博客:http://www.cnblogs.com/Eva-J/articles/8244551.html#_label6 server端 import socket import os impo ...

  10. Hadoop Yarn配置项 yarn.nodemanager.resource.local-dirs探讨

    1. What is the recommended value for "yarn.nodemanager.resource.local-dirs"? We only have ...