vue2源码分析:patch函数
目录
1.patch函数的脉络
2.类vnode的设计
3.createPatch函数中的辅助函数和patch函数
4.源码运行展示(DEMO)
一.patch函数的脉络
首先梳理一下patch函数的脉络。
第一,patch核心函数createPatchFunction,
然后,runtime/index.js中将patch方法挂载到vue的原型属性__patch__上。
Vue.prototype.__patch__ = inBrowser ? patch : noop
最后patch的使用是当我们调用vue实例的$el时,即调用patch函数。
if (!prevVnode) {
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
} else {
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
其中,createPatchFunction函数结构
export function createPatchFunction (backend) {
let i, j
const cbs = {} const { modules, nodeOps } = backend; ,,,hooks和modules的 for循环
其中const hooks = ['create', 'activate', 'update', 'remove', 'destroy'] 一些辅助函数
emptyNodeAt,createRmCb,removeNode,isUnknownElement,createElm,createComponent ,
initComponent,reactivateComponent, insert, createChildren ,isPatchable ,setScope ,
addVnodes ,invokeDestroyHook , removeVnodes , removeAndInvokeRemoveHook,updateChildren,
checkDuplicateKeys, findIdxInOld , patchVnode , invokeInsertHook ,hydrate, assertNodeMatch
核心函数return patch
}
第一,要了解createPatchFunction的参数backend。backend的nodeOps是节点的功能函数,包括createElement创建元素、removeChild删除子元素,tagName获取到标签名等,backend的modules是vue框架用于分别执行某个渲染任务的功能函数。
根据详细的截图,可以看到每个模块完成某个功能,属性和类、监听器、DOM属性、样式的创建和更新、指令更新以及其他操作
我们知道vue虚拟DOM的比较依赖于diff算法,diff算法到底有什么魔法能快速比较出文本的差异?我们可以手动的写一个简易的函数实现diff算法。具体可参照https://www.cnblogs.com/MRRAOBX/articles/10043258.html。
首先,我们先假设一个需求。
<div class = "box">
<ul>
<li> hello,everyone!</li>
</ul>
</div> var list = document.querySelector( '.list' )
var li = document.createElement( 'LI' )
li.innerHTML = ' 疫情还没有结束 ' list.appendChild( li )
我们用一个vdom对象模拟上述html结构,并通过render函数渲染出来。然后 数据更改了,data.name = ‘疫情终于结束了’
var vdom = {
tag: 'div',
attr: {
className: 'box'
},
content: [
{
tag: 'ul',
content: [
{
tag: 'li',
content: data.name
}
]
}
]
}
那么我们通过diff算法比对两次vdom,生成patch对象,最终实现了打补丁。
二.类vnode的设计
VNode类定义了很多属性。
export default class VNode {
tag: string | void;
data: VNodeData | void;
// VNode类定义了属性tag
constructor (){}
.......
}
同时提供了提供了一些功能,createEmptyVNode创建空的VNode,createTextVNode创建文本类型的VNode,cloneVNode克隆VNode。
为了方便我们更好的理解这个属性,我们可以运行源码,打印一下这个Vnode。我们是不是可以看到最重要的属性就是tag(标签名)、data(标签的属性-值)、children(所有后代元素)、context(上下文对象)。
附我的html结构
<div id="app">
<div></div>
。。。。。。
</div>
三.createPatch函数中的辅助函数和patch函数
createPatch函数包括有关VNode增删改查的功能函数
//返回的e
function emptyNodeAt (elm) {
return new VNode(nodeOps.tagName(elm).toLowerCase(), {}, [], undefined, elm)
}
//使用它的地方只有一个
oldVnode = emptyNodeAt(oldVnode);
emptyNodeAt包装oldVnode前后有什么区别呢?依然是运行源码,我们发现传入的参数是dom元素,包装后变成了VNode,即vue形式的节点实例。
createRmCb功能是创建remove函数
remove$$1函数作为一个对象,第一个参数是vnode所属的dom元素,第二个参数是监听器个数。内部实现remove函数拥有listeners属性,等到这个属性的值每一次减少直到0时将直接移除节点。这个原理很简单,要移除某个节点,先要把监听器一个一个的全部移除掉。
rm = createRmCb(vnode.elm, listeners);
//只有一个地方使用了createRmCb
'function createRmCb (childElm, listeners) {
function remove$$1 () {
if (--remove$$1.listeners === 0) {
removeNode(childElm);
}
}
remove$$1.listeners = listeners;
return remove$$1
}
removeNode移除节点,先找到父节点,然后通过removeChild移除掉这个节点。那么为什么要这样操作呢?因为这里的removeChild是原生方法中移除的唯一做法。
function removeNode (el) {
const parent = nodeOps.parentNode(el)
// element may have already been removed due to v-html / v-text
if (isDef(parent)) {
nodeOps.removeChild(parent, el)
}
}
function removeChild (node, child) {
node.removeChild(child);
}isUnknownElement略。
create***函数
createElm第一个参数是vue node实例,在vnode.js文件中我们已经知道了vnode类的具体情况,第二个参数是数组,表示插入的vnode实例的队列,第三个参数是parentElm父元素,毕竟原生的
添加元素唯一的方法是先找到父元素,然后appendChild添加元素。第4个参数是refElm,如果子元素包含ref属性的节点,那么这个参数就有值。第5个参数是nested,值是true或者false.第5个
参数是ownerArray,它是当前节点和兄弟节点组成的数组。第6个是index索引。
function createElm (
vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index
) {
if (isDef(vnode.elm) && isDef(ownerArray)) {
// This vnode was used in a previous render!
// now it's used as a new node, overwriting its elm would cause
// potential patch errors down the road when it's used as an insertion
// reference node. Instead, we clone the node on-demand before creating
// associated DOM element for it.
vnode = ownerArray[index] = cloneVNode(vnode)
}
首先我们对某一种类型的vnode进行了调整。一般情况下vnode的elm都有定义,不过当我用vnode.elm打印时返回undefined(具体原因还不知道,明明打印出来的vnode的elm属性的呀)。另外,ownerArray有哪些元素不会定义呢,答案是vue项目挂载app的根元素。这样一来,普通的vnode都不会进入这个if语句。
vnode.isRootInsert = !nested // for transition enter check
//根据注释,它跟vue画面的渐进效果有关
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
//如果是创建组件,那么直接返回
具体看后面createComponent的功能咯。
const data = vnode.data
const children = vnode.children
const tag = vnode.tag
if (isDef(tag)) {
if (process.env.NODE_ENV !== 'production') {
if (data && data.pre) {
creatingElmInVPre++
}
if (isUnknownElement(vnode, creatingElmInVPre)) {
warn(
'Unknown custom element: <' + tag + '> - did you ' +
'register the component correctly? For recursive components, ' +
'make sure to provide the "name" option.',
vnode.context
)
}
}
这一段就是把需要的数据从vnode中取出来,我们上面已经打印过vnode了,复习一下,data 是有关元素key-value的数据信息,chidren是后代元素,tag是标签名。并有针对开发环境的调试信息。
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode)
setScope(vnode)
//namespce命名空间
接下来,weex直接略过。、
else {
createChildren(vnode, children, insertedVnodeQueue)
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue)
}
insert(parentElm, vnode.elm, refElm)
}
那么我们看到创建元素调用的核心函数是createChildren和insert。
function createChildren (vnode, children, insertedVnodeQueue) {
//若
if (Array.isArray(children)) {
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(children)
}
//
for (let i = 0; i < children.length; ++i) {
createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
}
}
//如果是原生类型
else if (isPrimitive(vnode.text)) {
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
}
}
createChildren
function insert (parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) {
if (nodeOps.parentNode(ref) === parent) {
nodeOps.insertBefore(parent, elm, ref)
}
} else {
nodeOps.appendChild(parent, elm)
}
}
}
function appendChild (node, child) {
node.appendChild(child);
}
function insertBefore (parentNode, newNode, referenceNode) {
parentNode.insertBefore(newNode, referenceNode);
}
insert
function insert (parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) {
//若ref节点的父元素等于该元素的父元素
if (nodeOps.parentNode(ref) === parent) {
//那么通过insertBefore方法将元素ref插入到elm之前
nodeOps.insertBefore(parent, elm, ref)
}
} else {
//添加元素elm
nodeOps.appendChild(parent, elm)
}
}
}
//调用insert的例子
vnode.elm = nodeOps.createComment(vnode.text)
insert(parentElm, vnode.elm, refElm)
到底vue是如何创建元素的?我们用简单的html结构看一下createElm到底是如何运行的(我通过源码打断点的方式来看到底发生了什么)
new Vue({
el:"#app",}
);
//html结构
<div id="app">
<span>123</span>
</div>
vue项目初始化时首先创建div#app的节点。vnode是div#app的vnode,insertedVnodeQueue为空数组,parentElm是body元素,refElm如图,refElm到底是什么?它是一个文本节点。
wholeText: "↵"
assignedSlot: null
data: "↵"
length: 1
previousElementSibling: div#app
nextElementSibling: script
nodeType: 3
nodeName: "#text"
baseURI: "http://localhost:63342/vuesrc/1.vue.set%E4%BD%BF%E7%94%A8.html?_ijt=clboq4te5mp0i755tqhvsc3q75"
isConnected: true
ownerDocument: document
parentNode: body
parentElement: body
childNodes: NodeList []
firstChild: null
lastChild: null
previousSibling: div#app
nextSibling: script
nodeValue: "↵"
textContent: "↵"
__proto__: Text
第二个创建的元素是span。span的refElm是null,nested为true。
第三个创建的是123所代表的文本节点。
我们看到当vue项目要加载某些节点时都会调用它。
createComponent的使用在createElm这一行有这个判断。
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data;
if (isDef(i)) {
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */);
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true
}
}
}
首先是div#app元素。
在createComponent中判断vnode.data。div#app判断isDef(i)为true。
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
isReactivated和判断hook和init的if都会返回false。第二个if由于componentInstance: undefined也会false。
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef(i = i.hook) && isDef(i = i.init)) {
第二是span以及文本节点,他们由于data未定义,所以并不会进入外层if语句。
isPatchable
function isPatchable (vnode) {
while (vnode.componentInstance) {
vnode = vnode.componentInstance._vnode
}
return isDef(vnode.tag)
}
invokeCreateHooks
div#app的创建时会调用invokeCreateHooks
cbs的内容是
create: (8) [ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ]
activate: [ƒ]
update: (7) [ƒ, ƒ, ƒ, ƒ, ƒ, ƒ, ƒ]
remove: [ƒ]
destroy: (2) [ƒ, ƒ]
__proto__: Object
。。。。 create: Array(8)
0: ƒ updateAttrs(oldVnode, vnode)
1: ƒ updateClass(oldVnode, vnode)
2: ƒ updateDOMListeners(oldVnode, vnode)
3: ƒ updateDOMProps(oldVnode, vnode)
4: ƒ updateStyle(oldVnode, vnode)
5: ƒ _enter(_, vnode)
6: ƒ create(_, vnode)
7: ƒ updateDirectives(oldVnode, vnode)
length:
__proto__: Array(0)
那么函数调用后发生了什么呢?cbs.create是一个函数作为成员的数组,遍历每个成员调用,我们以其中一个成员函数来看看发生了什么,updateAttrs(emptyNode,vnode)。
function invokeCreateHooks (vnode, insertedVnodeQueue) {
for (let i = 0; i < cbs.create.length; ++i) {
cbs.create[i](emptyNode, vnode)
}
i = vnode.data.hook // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) i.create(emptyNode, vnode)
if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
}
}
我们找到updateAttrs方法。
function updateAttrs (oldVnode, vnode) {
var opts = vnode.componentOptions;
if (isDef(opts) && opts.Ctor.options.inheritAttrs === false) {
return
}
if (isUndef(oldVnode.data.attrs) && isUndef(vnode.data.attrs)) {
return
}
var key, cur, old;
var elm = vnode.elm;
var oldAttrs = oldVnode.data.attrs || {};
var attrs = vnode.data.attrs || {};
// clone observed objects, as the user probably wants to mutate it
if (isDef(attrs.__ob__)) {
attrs = vnode.data.attrs = extend({}, attrs);
}
//核心代码,setAttr设置新节点的属性
for (key in attrs) {
cur = attrs[key];
old = oldAttrs[key];
if (old !== cur) {
setAttr(elm, key, cur);
}
}
// #4391: in IE9, setting type can reset value for input[type=radio]
// #6666: IE/Edge forces progress value down to 1 before setting a max
/* istanbul ignore if */
if ((isIE || isEdge) && attrs.value !== oldAttrs.value) {
setAttr(elm, 'value', attrs.value);
}
//核心代码,删除纠结点的属性
for (key in oldAttrs) {
if (isUndef(attrs[key])) {
if (isXlink(key)) {
elm.removeAttributeNS(xlinkNS, getXlinkProp(key));
} else if (!isEnumeratedAttr(key)) {
elm.removeAttribute(key);
}
}
}
}
function setAttr (el, key, value) {
if (el.tagName.indexOf('-') > -1) {
baseSetAttr(el, key, value);
} else if (isBooleanAttr(key)) {
// set attribute for blank value
// e.g. <option disabled>Select one</option>
if (isFalsyAttrValue(value)) {
el.removeAttribute(key);
} else {
// technically allowfullscreen is a boolean attribute for <iframe>,
// but Flash expects a value of "true" when used on <embed> tag
value = key === 'allowfullscreen' && el.tagName === 'EMBED'
? 'true'
: key;
el.setAttribute(key, value);
}
} else if (isEnumeratedAttr(key)) {
el.setAttribute(key, convertEnumeratedValue(key, value));
} else if (isXlink(key)) {
if (isFalsyAttrValue(value)) {
el.removeAttributeNS(xlinkNS, getXlinkProp(key));
} else {
el.setAttributeNS(xlinkNS, key, value);
}
} else {
baseSetAttr(el, key, value);
}
}
setAttr
function baseSetAttr (el, key, value) {
if (isFalsyAttrValue(value)) {
el.removeAttribute(key);
} else {
// #7138: IE10 & 11 fires input event when setting placeholder on
// <textarea>... block the first input event and remove the blocker
// immediately.
/* istanbul ignore if */
if (
isIE && !isIE9 &&
el.tagName === 'TEXTAREA' &&
key === 'placeholder' && value !== '' && !el.__ieph
) {
var blocker = function (e) {
e.stopImmediatePropagation();
el.removeEventListener('input', blocker);
};
el.addEventListener('input', blocker);
// $flow-disable-line
el.__ieph = true; /* IE placeholder patched */
}
el.setAttribute(key, value);
}
}
然后就是data.hook有没有定义。要是定义了,那就调用create或者insert方法。
setScope
function setScope (vnode) {
let i
if (isDef(i = vnode.fnScopeId)) {
nodeOps.setStyleScope(vnode.elm, i)
} else {
let ancestor = vnode
while (ancestor) {
if (isDef(i = ancestor.context) && isDef(i = i.$options._scopeId)) {
nodeOps.setStyleScope(vnode.elm, i)
}
ancestor = ancestor.parent
}
}
// for slot content they should also get the scopeId from the host instance.
if (isDef(i = activeInstance) &&
i !== vnode.context &&
i !== vnode.fnContext &&
isDef(i = i.$options._scopeId)
) {
nodeOps.setStyleScope(vnode.elm, i)
}
}
addVnodes
function addVnodes (parentElm, refElm, vnodes, startIdx, endIdx, insertedVnodeQueue) {
for (; startIdx <= endIdx; ++startIdx) {
createElm(vnodes[startIdx], insertedVnodeQueue, parentElm, refElm, false, vnodes, startIdx)
}
}
invokeDestroyHook
function invokeDestroyHook (vnode) {
let i, j
const data = vnode.data
if (isDef(data)) {
if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode)
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode)
}
if (isDef(i = vnode.children)) {
for (j = 0; j < vnode.children.length; ++j) {
invokeDestroyHook(vnode.children[j])
}
}
}
destroy调用实际上是调用的function destory以及unbindDirectives 。那么功能是销毁咯。
destroy: Array(2)
0: ƒ destroy(vnode)
1: ƒ unbindDirectives(vnode)
destroy: function destroy (vnode) {
var componentInstance = vnode.componentInstance;
if (!componentInstance._isDestroyed) {
if (!vnode.data.keepAlive) {
componentInstance.$destroy();
} else {
deactivateChildComponent(componentInstance, true /* direct */);
}
}
}
destroy: function unbindDirectives (vnode) {
updateDirectives(vnode, emptyNode);
}
removeVnodes删除vnode做了哪些事情,删除hook,删除元素。
function removeVnodes (parentElm, vnodes, startIdx, endIdx) {
for (; startIdx <= endIdx; ++startIdx) {
const ch = vnodes[startIdx]
if (isDef(ch)) {
if (isDef(ch.tag)) {
removeAndInvokeRemoveHook(ch)
invokeDestroyHook(ch)
} else { // Text node
removeNode(ch.elm)
}
}
}
}
removeNode的原生方法其实就是removeChild。
function removeNode (el) {
var parent = nodeOps.parentNode(el);
// element may have already been removed due to v-html / v-text
if (isDef(parent)) {
nodeOps.removeChild(parent, el);
}
}
rm一开始为undefined,通过 rm = createRmCb(vnode.elm, listeners) 创建了remove函数。
核心代码是 cbs.remove[i](vnode, rm) 其实就回到了remove函数这里。
function remove () {
if (--remove.listeners === 0) {
removeNode(childElm)
}
}
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 <transition-group>
// to ensure removed elements stay in correct relative positions
// during leaving transitions
const canMove = !removeOnly if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(newCh)
} 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)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
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
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
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]
if (sameVnode(vnodeToMove, newStartVnode)) {
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
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
if (oldStartIdx > oldEndIdx) {
refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)
}
}
checkDkeys
function checkDuplicateKeys (children) {
const seenKeys = {}
for (let i = 0; i < children.length; i++) {
const vnode = children[i]
const key = vnode.key
if (isDef(key)) {
if (seenKeys[key]) {
warn(
`Duplicate keys detected: '${key}'. This may cause an update error.`,
vnode.context
)
} else {
seenKeys[key] = true
}
}
}
}
findIdsInOld
function findIdxInOld (node, oldCh, start, end) {
for (let i = start; i < end; i++) {
const c = oldCh[i]
if (isDef(c) && sameVnode(node, c)) return i
}
}
patchVnode
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
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)) {
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)) {
if (isDef(oldCh) && isDef(ch)) {
if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly)
} else if (isDef(ch)) {
if (process.env.NODE_ENV !== 'production') {
checkDuplicateKeys(ch)
}
if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')
addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
} else if (isDef(oldCh)) {
removeVnodes(elm, 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)) {
if (isDef(i = data.hook) && isDef(i = i.postpatch)) i(oldVnode, vnode)
}
}
invokeInsertHook
function invokeInsertHook (vnode, queue, initial) {
// delay insert hooks for component root nodes, invoke them after the
// element is really inserted
if (isTrue(initial) && isDef(vnode.parent)) {
vnode.parent.data.pendingInsert = queue
} else {
for (let i = 0; i < queue.length; ++i) {
queue[i].data.hook.insert(queue[i])
}
}
}
assertNodeMatch
function assertNodeMatch (node, vnode, inVPre) {
if (isDef(vnode.tag)) {
return vnode.tag.indexOf('vue-component') === 0 || (
!isUnknownElement(vnode, inVPre) &&
vnode.tag.toLowerCase() === (node.tagName && node.tagName.toLowerCase())
)
} else {
return node.nodeType === (vnode.isComment ? 8 : 3)
}
}
核心函数patch
首先,通过示例给patch函数打断点,我们看到第一个参数是div#app dom元素,第二个参数是包含div#app信息的vnode。第一部分的代码并没有进入if语句
if (isUndef(vnode)) {
if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
return
} let isInitialPatch = false
const insertedVnodeQueue = []
第二部分由于oldNode已经定义所以分支语句进入else分支。else分支首先处理如果oldVnode是元素的一些操作。然后createElm创建元素。第三,如果存在父元素,对祖先元素遍历,那么对祖先元素注册钩子函数,否则世界registerRef。 ancestor = ancestor.parent 是while循环的条件。接下来删除旧的节点。第四,invokeInsertHook。最后返回vnode的dom元素。
if (isUndef(oldVnode)){}else{
//dom元素的nodeType为1,所以isDef返回true
const isRealElement = isDef(oldVnode.nodeType) if (!isRealElement && sameVnode(oldVnode, vnode)) {
// patch existing root node
patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly)
}
//!isRealElement为false,进入else分支
else {
if (isRealElement) {
// mounting to a real element
// check if this is server-rendered content and if we can perform
// a successful hydration.
//根据var SSR_ATTR = 'data-server-rendered',我们看到如果是服务端渲染
//那么元素移除掉SSR-ATTR属性,并且hydrating设置为true
if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
oldVnode.removeAttribute(SSR_ATTR)
hydrating = true
}
//如果我们要设置hydrating,那么就插入钩子函数
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 ' +
'<p>, or missing <tbody>. Bailing hydration and performing ' +
'full client-side render.'
)
}
}
// either not server-rendered, or hydration failed.
// create an empty node and replace it
//emptyNodeAt将oldVnode包装一下
oldVnode = emptyNodeAt(oldVnode)
} // replacing existing element
const oldElm = oldVnode.elm
const parentElm = nodeOps.parentNode(oldElm) // 创建新节点create new node
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(parentElm, [oldVnode], 0, 0)
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode)
}
}
}
四.源码运行展示
虚拟DOM并不能改变DOM操作本身很慢的情况,它通过对象模拟DOM节点,它的优化点有两个部分
初始化文档结构时,先js构建出一个真实的DOM结构,然后再插入文档。
更新试图时,将新旧节点树比较计算出最小变更然后再映射到真实的DOM中。这在大量、频繁的更新数据时有很大的优势。
这也是patch函数的功能。
DEMO1.初次渲染
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue初次渲染</title>
<script src="js/vue.js"></script>
</head>
<body>
<div id="app">
<span>{{obj}}</span>
</div>
<script>
new Vue({
el:"#app",
data:{
obj:"012"
},
created:function(){
this.obj="567";
},
methods:{
addName(){
this.obj2=this.obj2+"456"
}
}
})
</script>
</body>
</html>
我们把vue.js打断点。
首先在function lifecycleMixin 中调用 vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
其中 Vue.prototype.__patch__ = inBrowser ? patch : noop; 目前我们只考虑浏览器有DOM的情况。vm.$el就是div#app节点,vnode是div#app包装成的虚拟节点。
然后执行patch函数,
if (isUndef(vnode)) {
if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
return
} var isInitialPatch = false;
var insertedVnodeQueue = []; if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true;
createElm(vnode, insertedVnodeQueue);
}
//这些逻辑都不会进入
由于oldNode参数是div#app,它是真正的元素节点,emptyNodeAt之后什么变化呢?它将dom节点变成虚拟节点。
if (isRealElement) {
//SSR渲染的逻辑略过。
oldVnode = emptyNodeAt(oldVnode); }
然后createElm,这个函数的核心代码是 insert(parentElm, vnode.elm, refElm) 那么我们的节点vnode.elm就插入了DOM中。
var oldElm = oldVnode.elm;
var parentElm = nodeOps.parentNode(oldElm); // create new node创建新节点
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)
);
function insert (parent, elm, ref) {
if (isDef(parent)) {
if (isDef(ref)) {
if (nodeOps.parentNode(ref) === parent) { nodeOps.insertBefore(parent, elm, ref)
}
} else {
nodeOps.appendChild(parent, elm)
}
}
}
//通过insertBefore或者appendChild添加元素
由于vue项目挂载的节点的parent为undefined,所以 if (isDef(vnode.parent)) { 为false不进入。
然后挂载的节点的父元素是body,存在即true,那么删除旧的节点。
if (isDef(parentElm)) {
removeVnodes(parentElm, [oldVnode], 0, 0)
}
为什么要删除旧的节点?
因为createElm加入的节点是与虚拟DOM关联的节点,浏览器本身还有渲染节点的。从图示打断点,当运行到removeVnodes时,这个时候还未删除就出现了两行元素。当我们运行完所有代码后才能显示正常结果。
正常结果图示
最后 invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch) 将队列中的钩子函数插入到队列的hook中。
function invokeInsertHook (vnode, queue, initial) {
// delay insert hooks for component root nodes, invoke them after the
// element is really inserted
if (isTrue(initial) && isDef(vnode.parent)) {
vnode.parent.data.pendingInsert = queue;
} else {
for (var i = 0; i < queue.length; ++i) {
queue[i].data.hook.insert(queue[i]);
}
}
}
DEMO2.
需求是我们要展示一个个产品列表,而且我们这个DEMO使用模块化开发的方式。我们首先来看一看初次渲染的情况。
先上代码。目录结构是vue官方脚手架。
核心代码是
//App.vue
<template>
<div>
<img src="./assets/logo.png">
<ul>
<li v-for="item in items">
{{ item.message }}---{{item.id}}
</li>
</ul>
<!--<router-view/>-->
</div>
</template> <script>
import Vue from "vue"
export default {
name: 'App', data(){
return{
items:[
{id:1101,message:"VERSACE范思哲"},
{id:1102,message:"GUCCI古驰男士经典蜜蜂刺绣"},
{id:1103,message:"BURBERRY巴宝莉男士休闲长袖衬衫"},
{id:1104,message:"BALLY巴利奢侈品男包"},
{id:1105,message:"FERRAGAMO菲拉格慕男款休闲皮鞋"}
]
}
},
methods:{
}
}
</script> <style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
li{
list-style: none;
}
</style>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>vue-demo</title>
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
我们依然在Sources面板找到模块中vue源码打断点。
oldNode的结构是
vnode的结构是
我们看到vnode的tag名称是vue-component-4-App。
if (isUndef(vnode)) {
if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); }
return
} var isInitialPatch = false;
var insertedVnodeQueue = [];
//打头的代码,逻辑不会进入
if (isUndef(oldVnode)) {
// empty mount (likely as component), create new root element
isInitialPatch = true;
createElm(vnode, insertedVnodeQueue);
} else {
//核心代码
oldVnode = emptyNodeAt(oldVnode);
}
emptyNodeAt将原有的节点,同时也是DOM节点包装成虚拟节点。
// replacing existing element
var oldElm = oldVnode.elm;
var parentElm = nodeOps.parentNode(oldElm);
//parentElm是undefined
//创建新节点
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)
);
进入createElm函数。vnode是tag名为vue-component-4-App的虚拟节点。parentElm是body元素。
createElm函数中由于ownerArray等于undefined,所以打头的if语句为false。接下来到createComponent函数。
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
return
}
if (isDef(i)) {
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
//根据vnode.data的结构,通过赋值,i调用的是init钩子函数。
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */);
}
// after calling the init hook, if the vnode is a child component
// it should've created a child instance and mounted it. the child
// component also has set the placeholder vnode's elm.
// in that case we can just return the element and be done.
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true
}
}
那么初始化init钩子函数调用, child.$mount(hydrating ? vnode.elm : undefined, hydrating); 由于hydrating为false,进而进入mount函数。
mountComponent执行了 callHook(vm, 'beforeMount'); 然后运行了update。接下来挂载了watcher。
updateComponent = function () {
vm._update(vm._render(), hydrating);
};
new Watcher(vm, updateComponent, noop, {
before: function before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate');
}
}
}, true /* isRenderWatcher */);
然后又回到了createElm函数。
这里的vnode指的是template中的包裹元素。它的父元素是刚才的tag为vue-component-4-App的元素。
//vnode结构
child: (...)
tag: "div"
data: undefined
children: (3) [VNode, VNode, VNode]
text: undefined
elm: undefined
ns: undefined
context: VueComponent {_uid: 1, _isVue: true, $options: {…}, _renderProxy: Proxy, _self: VueComponent, …}
fnContext: undefined
fnOptions: undefined
fnScopeId: undefined
key: undefined
componentOptions: undefined
componentInstance: undefined
parent: VNode {tag: "vue-component-4-App", data: {…}, children: undefined, text: undefined, elm: undefined, …}
raw: false
isStatic: false
isRootInsert: true
isComment: false
isCloned: false
isOnce: false
asyncFactory: undefined
asyncMeta: undefined
isAsyncPlaceholder: false
__proto__: Object
<template>
<div>
<img src="./assets/logo.png">
<ul>
<li v-for="item in items">
{{ item.message }}---{{item.id}}
</li>
</ul>
<!--<router-view/>-->
</div>
</template>
这时 createChildren(vnode, children, insertedVnodeQueue); 创建各个子元素。通过遍历,最终会将所有子元素通过insert添加到tag为vue-component-4-App的元素上。
最终patch函数返回 return vnode.elm 节点。
从这个分析可以看到初次渲染,会把所有节点最终加入template中的div元素,等到了tag为vue-component-4-App的元素,由于isDef(parentElm)的parentElm为body元素,所以为true。这个时候也可以看到DOM元素有两份,那么就要删除旧的元素 removeVnodes(parentElm, [oldVnode], 0, 0); 。最终运行完毕,呈现正确的DOM结构。
当还没有运行removeVnodes时DOM结构如截图2。
图1
图2
运行完removeVnodes后原有的div#app就被删除了。
初次渲染我们也可以看到,总是把所有子元素构成的render树渲染好了再一次性添加到文档中。
DEMO3
需求是ul中动态删除某个li标签。我们知道要使用唯一ID的key,才能更高效的渲染。我们可以来看一下patch函数中到底发生了什么?
其他内容同DEMO2,也是按模块化开发来的。
//App.vue
<template>
<div>
<img src="./assets/logo.png">
<ul>
<li v-for="item in items">
{{ item.message }}---{{item.id}}
</li>
</ul>
<button v-on:click="addItem()">添加item</button>
<!--<router-view/>-->
</div>
</template> <script>
import Vue from "vue"
export default {
name: 'App', data(){
return{
items:[
{id:1101,message:"VERSACE范思哲"},
{id:1102,message:"GUCCI古驰男士经典蜜蜂刺绣"},
{id:1103,message:"BURBERRY巴宝莉男士休闲长袖衬衫"},
{id:1104,message:"BALLY巴利奢侈品男包"},
{id:1105,message:"FERRAGAMO菲拉格慕男款休闲皮鞋"}
]
}
},
methods:{
addItem(){
this.items.splice(2,1,{id:1106,message:"GUCCI古奇新款小蜜蜂刺绣低帮休闲板鞋男"})
} } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } li{ list-style: none; } </style>
点击按钮 this.items.splice(2,1) 就会添加一个item。
我们这次在function renderList打断点。
//App.vue
<template>
<div>
<img src="./assets/logo.png">
<ul>
<li v-for="item in items" >
{{ item.message }}---{{item.id}}
</li>
</ul>
<button v-on:click="addItem()">添加item</button>
<!--<router-view/>-->
</div>
</template> <script>
import Vue from "vue"
export default {
name: 'App', data(){
return{
items:[
{id:1101,message:"VERSACE范思哲"},
{id:1102,message:"GUCCI古驰男士经典蜜蜂刺绣"},
{id:1103,message:"BURBERRY巴宝莉男士休闲长袖衬衫"},
{id:1104,message:"BALLY巴利奢侈品男包"},
{id:1105,message:"FERRAGAMO菲拉格慕男款休闲皮鞋"}
]
}
},
methods:{
addItem(){
this.items.push({id:1106,message:"GUCCI古奇新款小蜜蜂刺绣低帮休闲板鞋男"});
}
}
}
</script> <style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
li{
list-style: none;
}
</style>
首先看初次渲染时的参数情况。val为包含5个子元素的类数组。进入第一个if分支,render返回li标签的虚拟节点,节点含有并且含有key属性,并添加到ret数组。
if (Array.isArray(val) || typeof val === 'string') {
ret = new Array(val.length);
for (i = 0, l = val.length; i < l; i++) {
ret[i] = render(val[i], i);
}
}
如果我们push新的值,ret为6个元素了。那么接下来就会打断点运行到patchVnode,其中sameVnode通过key来比较是否是同一个节点。
function sameVnode (a, b) {
return (
a.key === b.key && (
(
a.tag === b.tag &&
a.isComment === b.isComment &&
isDef(a.data) === isDef(b.data) &&
sameInputType(a, b)
) || (
isTrue(a.isAsyncPlaceholder) &&
a.asyncFactory === b.asyncFactory &&
isUndef(b.asyncFactory.error)
)
)
)
//如果旧的虚拟节点和新的节点是相同的,那么不用作渲染。
if (oldVnode === vnode) {
return
}
更详细的参考一些v-for指令的源码,这里只涉及patch函数相关的。
vue2源码分析:patch函数的更多相关文章
- 性能测试分享: Jmeter的源码分析main函数参数
性能测试分享: Jmeter的源码分析main函数参数 poptest是国内唯一一家培养测试开发工程师的培训机构,以学员能胜任自动化测试,性能测试,测试工具开发等工作为目标.如果对课程感兴趣,请大 ...
- jQuery源码分析-each函数
本文部分截取自且行且思 jQuery.each方法用于遍历一个数组或对象,并对当前遍历的元素进行处理,在jQuery使用的频率非常大,下面就这个函数做了详细讲解: 复制代码代码 /*! * jQuer ...
- Vue2源码分析-逻辑梳理
很久之前就看完vue1,但是太懒就一直没写博客,这次打算抽下懒筋先把自己看过了记录下来,否则等全部看完,估计又没下文了 看源码总需要抱着一个目的,否则就很难坚持下去,我并没做过vue的项目,我几乎很少 ...
- LiteOS-任务篇-源码分析-系统启动函数
目录 前言 链接 参考 开启调度 LOS_Start 函数源码 osTickStart 函数源码 LOS_StartToRun 函数源码 前言 20201009 移植好内核后,开始实战内核. 源码分析 ...
- LiteOS-任务篇-源码分析-任务调度函数
目录 前言 笔录草稿 核心源码分析 osTaskSchedule函数源码分析 osPendSV函数源码分析 TaskSwitch函数源码分析 调度上层源码分析 osSchedule函数源码分析 LOS ...
- Memcached源码分析——process_command函数解析
以下为个人笔记 /** * process_command 在memcached中是用来处理用户发送的命令的, * 包括get set,add,delete,replace,stats,flush_a ...
- jquery源码分析-工具函数
jQuery的版本一路狂飙啊,现在都到了2.0.X版本了.有空的时候,看看jquery的源码,学习一下别人的编程思路还是不错的. 下面这里是一些jquery的工具函数代码,大家可以看看,实现思路还是很 ...
- 函数适配器bind2nd 、mem_fun_ref 源码分析、函数适配器应用举例
一.适配器 三种类型的适配器: 容器适配器:用来扩展7种基本容器,利用基本容器扩展形成了栈.队列和优先级队列 迭代器适配器:(反向迭代器.插入迭代器.IO流迭代器) 函数适配器:函数适配器能够将仿函数 ...
- 【LiteOS】LiteOS任务篇-源码分析-创建任务函数
目录 前言 链接 参考 笔录草稿 部分源码分析 源码分析 LOS_TaskCreate函数 LOS_TaskCreateOnly函数 宏 OS_TCB_FROM_PENDLIST 和 宏 LOS_DL ...
随机推荐
- C++走向远洋——20(项目一,三角形,类)
*/ * Copyright (c) 2016,烟台大学计算机与控制工程学院 * All rights reserved. * 文件名:sanjiaoxing.cpp * 作者:常轩 * 微信公众号: ...
- 仿segmentfault-table横向滚动
问题描述 自己的博客在用移动端访问时,如果table的列数足够多会显示不全,如下图红圈所示 正常情况如图 解决过程 使用chrome发现segmentfault的解决方法是在table上套一个tabl ...
- 前端每日实战:61# 视频演示如何用纯 CSS 创作一只咖啡壶
效果预览 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/ZRjGGy 可交互视频 此视频是可 ...
- js数组冒泡排序、快速排序、插入排序
1.冒泡排序 //第一种 function bubblesort(ary){ for(var i=0;i<ary.length-1;i++){ for(var j=0;j<ary.leng ...
- 我折腾的shell笔记
目录 Mac一些常用的快捷键记录 iTerm2或者命令行相关 Mac桌面上或者某目录下操作 一些实用脚本示例 代码无提示或者其他抽风症状,清除Xcode缓存 查看当前网络ip地址 日常提交推送git代 ...
- 开源网站云查杀方案,搭建自己的云杀毒-搭建ClamAV服务器
开源网站云查杀方案,搭建自己的云杀毒 搭建ClamAV服务器 1 前言: 在上一篇我们已经演示了整个方案,传送门<开源网站云查杀方案,搭建自己的云杀毒>:https://ww ...
- 老式车载导航如何支持大于4G的SD卡
这个知识点以后会越来越没什么用,因为这类导航慢慢就会消失.记录这个,就是提醒自己如何防止以为很懂而被骗. 随着导航地图越来越大,4G的SD卡很快就不够用了,但是很不幸车载导航款式太老了,不支持大于4G ...
- HashSet底层、及存入对象时候如何保持唯一
HashSet底层.及存入对象时候如何保持唯一 在JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里. 但是当位于一个桶中的元素较多,即hash ...
- ASP.net MVC 构建layui管理后台(构造基础仓储)<1>
本文章为ASP.net MVC 构建layui管理后台,第一篇. 使用EF+ado.net 实体数据模型模式进行底层的数据库连接. 在项目添加一个类库Model 在类库Model上添加一个ado.ne ...
- Redis基本数据类型、数据持久化、过期策略及淘汰机制
一点技术.技术乐享!!! 如果有人问你:Redis这么快,他的“多线程模式”你了解吗? 请回答他:您是想问Redis这么快,为什么还是单线程模式吗? redis是什么 简单来说redis是C语言开发的 ...