The content of this article is about how to implement virtual DOM? (Code sample) has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.
This article reads and analyzes the source code of virtual-dom, and explains the structure of Virtual DOM and related Diff algorithms, so that readers can have a certain understanding of the entire data structure and related Diff algorithms.
We will reveal how the results of the Diff algorithm in Virtual DOM are mapped to the real DOM in the next blog.
The main content of this article is:
The structure of Virtual DOM and the Diff algorithm of Virtual DOM
Note: The implementation of this Virtual DOM is not the source code of React Virtual DOM, but Based on virtual-dom) this library. The two are similar in principle, and this library is simpler and easier to understand. Compared with this library, React has further optimized and adjusted Virtual DOM, which I will analyze in subsequent blogs.
Structure of Virtual DOM
VirtualNode
As the metadata structure of Virtual DOM, VirtualNode is located in the vnode/vnode.js file. Let's intercept a part of the declaration code to take a look at the internal structure:
function VirtualNode(tagName, properties, children, key, namespace) { this.tagName = tagName this.properties = properties || noProperties //props对象,Object类型 this.children = children || noChildren //子节点,Array类型 this.key = key != null ? String(key) : undefined this.namespace = (typeof namespace === "string") ? namespace : null ... this.count = count + descendants this.hasWidgets = hasWidgets this.hasThunks = hasThunks this.hooks = hooks this.descendantHooks = descendantHooks } VirtualNode.prototype.version = version //VirtualNode版本号,isVnode()检测标志 VirtualNode.prototype.type = "VirtualNode" // VirtualNode类型,isVnode()检测标志
The above is the complete structure of a VirtualNode, including specific tag names, attributes, child nodes, etc.
VText
VText is a plain text node, corresponding to the plain text in HTML. Therefore, this attribute only has one field: text.
function VirtualText(text) { this.text = String(text) } VirtualText.prototype.version = version VirtualText.prototype.type = "VirtualText"
VPatch
VPatch is a data structure that represents the operation records that need to be performed on Virtual DOM. It is located in the vnode/vpatch.js file. Let's take a look at the specific code inside:
// 定义了操作的常量,如Props变化,增加节点等 VirtualPatch.NONE = 0 VirtualPatch.VTEXT = 1 VirtualPatch.VNODE = 2 VirtualPatch.WIDGET = 3 VirtualPatch.PROPS = 4 VirtualPatch.ORDER = 5 VirtualPatch.INSERT = 6 VirtualPatch.REMOVE = 7 VirtualPatch.THUNK = 8 module.exports = VirtualPatch function VirtualPatch(type, vNode, patch) { this.type = Number(type) //操作类型 this.vNode = vNode //需要操作的节点 this.patch = patch //需要操作的内容 } VirtualPatch.prototype.version = version VirtualPatch.prototype.type = "VirtualPatch"
The constants define the operations on the VNode node. For example: VTEXT is to add a VText node, and PROPS is to change the Props attribute of the current node.
Virtual DOM’s Diff algorithm
Now that we understand the three structures in virtual DOM, let’s take a look at the Virtual DOM’s Diff algorithm.
This Diff algorithm is the core algorithm in Virtual DOM. By inputting the initial state A (VNode) and the final state B (VNode), this algorithm can get the change steps (VPatch) from A to B. Based on the obtained series of steps, we can know which nodes need to be added and which nodes Which nodes need to be deleted and whose attributes have changed. In this Diff algorithm, it is divided into three parts:
VNode’s Diff algorithm Props’ Diff algorithm Vnode children’s Diff algorithm
Below, we will introduce these Diff algorithms one by one.
VNode’s Diff algorithm
This algorithm is a comparison algorithm for a single VNode. It is used in the scenario of comparing a single node in two trees. The specific algorithm is as follows. If you don’t want to read the source code directly, you can also turn to the following. There will be relevant code flow instructions for your reference:
function walk(a, b, patch, index) { if (a === b) { return } var apply = patch[index] var applyClear = false if (isThunk(a) || isThunk(b)) { thunks(a, b, patch, index) } else if (b == null) { // If a is a widget we will add a remove patch for it // Otherwise any child widgets/hooks must be destroyed. // This prevents adding two remove patches for a widget. if (!isWidget(a)) { clearState(a, patch, index) apply = patch[index] } apply = appendPatch(apply, new VPatch(VPatch.REMOVE, a, b)) } else if (isVNode(b)) { if (isVNode(a)) { if (a.tagName === b.tagName && a.namespace === b.namespace && a.key === b.key) { var propsPatch = diffProps(a.properties, b.properties) if (propsPatch) { apply = appendPatch(apply, new VPatch(VPatch.PROPS, a, propsPatch)) } apply = diffChildren(a, b, patch, apply, index) } else { apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b)) applyClear = true } } else { apply = appendPatch(apply, new VPatch(VPatch.VNODE, a, b)) applyClear = true } } else if (isVText(b)) { if (!isVText(a)) { apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b)) applyClear = true } else if (a.text !== b.text) { apply = appendPatch(apply, new VPatch(VPatch.VTEXT, a, b)) } } else if (isWidget(b)) { if (!isWidget(a)) { applyClear = true } apply = appendPatch(apply, new VPatch(VPatch.WIDGET, a, b)) } if (apply) { patch[index] = apply } if (applyClear) { clearState(a, patch, index) } }
The specific logic of the code is as follows:
If a and b are this If the two VNodes are congruent, it is considered that there is no modification and returns directly.
If one of them is a thunk, use the thunk comparison method thunks.
If a is a widget and b is empty, then the remove operation of a and its child nodes is added to the patch recursively.
If b is a VNode,
If a is also a VNode, then compare tagName, namespace, key, and if they are the same, compare the Props of the two VNodes (using the diffProps algorithm mentioned below), Compare the children of two VNodes at the same time (using the diffChildren algorithm mentioned below); if they are different, directly add the insert operation of node b to the patch, and set the mark position to true.
If a is not a VNode, directly add the insert operation of node b to the patch, and set the mark position to true.
If b is VText, check whether the type of a is VText. If not, add the VText operation to the patch and set the flag bit to true; if it is and the text content is different, add the VText operation to the patch. The operation is added to the patch.
If b is a Widget, check whether the type of a is a widget. If so, set the flag to true. Regardless of the type of a, add the Widget operation to the patch.
Check the flag bit. If the flag is true, then add the remove operation of a and its child nodes to the patch through recursion.
This is the whole process of the diff algorithm of a single VNode node. This algorithm is the entrance to the entire diff algorithm, and the comparison of two trees starts from this algorithm.
Prpps’ Diff algorithm
After reading the diff algorithm of a single VNode node, let’s take a look at the diffProps
algorithm mentioned above.
This algorithm is a Props comparison algorithm for two compared VNode nodes. It is used when the key value and tag name are the same in both scenarios. The specific algorithm is as follows. If you don’t want to read the source code directly, you can also turn to the following. There will be relevant code flow instructions for your reference:
function diffProps(a, b) { var diff for (var aKey in a) { if (!(aKey in b)) { diff = diff || {} diff[aKey] = undefined } var aValue = a[aKey] var bValue = b[aKey] if (aValue === bValue) { continue } else if (isObject(aValue) && isObject(bValue)) { if (getPrototype(bValue) !== getPrototype(aValue)) { diff = diff || {} diff[aKey] = bValue } else if (isHook(bValue)) { diff = diff || {} diff[aKey] = bValue } else { var objectDiff = diffProps(aValue, bValue) if (objectDiff) { diff = diff || {} diff[aKey] = objectDiff } } } else { diff = diff || {} diff[aKey] = bValue } } for (var bKey in b) { if (!(bKey in a)) { diff = diff || {} diff[bKey] = b[bKey] } } return diff }
The specific logic of the code is as follows:
-
Traverse
a
objects.- 当key值不存在于
b
,则将此值存储下来,value赋值为undefined
。 - 当此key对应的两个属性都相同时,继续终止此次循环,进行下次循环。
- 当key值对应的value不同且key值对应的两个value都是对象时,判断Prototype值,如果不同则记录key对应的
b
对象的值;如果b
对应的value是hook
的话,记录b的值。 - 上面条件判断都不同且都是对象时,则继续比较key值对应的两个对象(递归)。
- 当有一个不是对象时,直接将
b
对应的value进行记录。
- 当key值不存在于
- 遍历
b
对象,将所有a
对象中不存在的key值对应的对象都记录下来。
整个算法的大致流程如下,因为比较简单,就不画相关流程图了。如果逻辑有些绕的话,可以配合代码食用,效果更佳。
Vnode children的Diff算法
下面让我们来看下最后一个算法,就是关于两个VNode节点的children属性的diffChildren
算法。这个个diff算法分为两个部分,第一部分是将变化后的结果b
的children进行顺序调整的算法,保证能够快速的和a
的children进行比较;第二部分就是将a
的children与重新排序调整后的b
的children进行比较,得到相关的patch。下面,让我们一个一个算法来看。
reorder算法
该算法的作用是将b
节点的children数组进行调整重新排序,让a
和b
两个children之间的diff算法更加节约时间。具体代码如下:
function reorder(aChildren, bChildren) { // O(M) time, O(M) memory var bChildIndex = keyIndex(bChildren) var bKeys = bChildIndex.keys // have "key" prop,object var bFree = bChildIndex.free //don't have "key" prop,array // all children of b don't have "key" if (bFree.length === bChildren.length) { return { children: bChildren, moves: null } } // O(N) time, O(N) memory var aChildIndex = keyIndex(aChildren) var aKeys = aChildIndex.keys var aFree = aChildIndex.free // all children of a don't have "key" if (aFree.length === aChildren.length) { return { children: bChildren, moves: null } } // O(MAX(N, M)) memory var newChildren = [] var freeIndex = 0 var freeCount = bFree.length var deletedItems = 0 // Iterate through a and match a node in b // O(N) time, for (var i = 0 ; i = bFree.length ? bChildren.length : bFree[freeIndex] // Iterate through b and append any new keys // O(M) time for (var j = 0; j = lastFreeIndex) { // Add any leftover non-keyed items newChildren.push(newItem) } } var simulate = newChildren.slice() var simulateIndex = 0 var removes = [] var inserts = [] var simulateItem for (var k = 0; k <p>下面,我们来简单介绍下这个排序算法:</p><ol> <li>检查<code>a</code>和<code>b</code>中的children是否拥有key字段,如果没有,直接返回<code>b</code>的children数组。</li> <li> <p>如果存在,初始化一个数组newChildren,遍历<code>a</code>的children元素。</p> <ol> <li>如果aChildren存在key值,则去bChildren中找对应key值,如果bChildren存在则放入新数组中,不存在则放入一个null值。</li> <li>如果aChildren不存在key值,则从bChildren中不存在key值的第一个元素开始取,放入新数组中。</li> </ol> </li> <li>遍历bChildren,将所有achildren中没有的key值对应的value或者没有key,并且没有放入新数组的子节点放入新数组中。</li> <li>将bChildren和新数组逐个比较,得到从新数组转换到bChildren数组的<code>move</code>操作patch(即<code>remove</code>+<code>insert</code>)。</li> <li>返回新数组和<code>move</code>操作列表。</li> </ol><p>通过上面这个排序算法,我们可以得到一个新的<code>b</code>的children数组。在使用这个数组来进行比较厚,我们可以将两个children数组之间比较的时间复杂度从o(n^2)转换成o(n)。具体的方法和效果我们可以看下面的DiffChildren算法。</p><h3 id="DiffChildren算法">DiffChildren算法</h3><pre class="brush:php;toolbar:false">function diffChildren(a, b, patch, apply, index) { var aChildren = a.children var orderedSet = reorder(aChildren, b.children) var bChildren = orderedSet.children var aLen = aChildren.length var bLen = bChildren.length var len = aLen > bLen ? aLen : bLen for (var i = 0; i <p>通过上面的重新排序算法整理了以后,两个children比较就只需在相同下标的情况下比较了,即aChildren的第N个元素和bChildren的第N个元素进行比较。然后较长的那个元素做<code>insert</code>操作(bChildren)或者<code>remove</code>操作(aChildren)即可。最后,我们将move操作再增加到patch中,就能够抵消我们在reorder时对整个数组的操作。这样只需要一次便利就得到了最终的patch值。</p><p style="white-space: normal;">总结</p><p>整个Virtual DOM的diff算法设计的非常精巧,通过三个不同的分部算法来实现了VNode、Props和Children的diff算法,将整个Virtual DOM的的diff操作分成了三类。同时三个算法又互相递归调用,对两个Virtual DOM数做了一次(伪)深度优先的递归比较。</p><p> </p>
The above is the detailed content of How to implement virtual DOM? (code example). For more information, please follow other related articles on the PHP Chinese website!

JavaScript is widely used in websites, mobile applications, desktop applications and server-side programming. 1) In website development, JavaScript operates DOM together with HTML and CSS to achieve dynamic effects and supports frameworks such as jQuery and React. 2) Through ReactNative and Ionic, JavaScript is used to develop cross-platform mobile applications. 3) The Electron framework enables JavaScript to build desktop applications. 4) Node.js allows JavaScript to run on the server side and supports high concurrent requests.

Python is more suitable for data science and automation, while JavaScript is more suitable for front-end and full-stack development. 1. Python performs well in data science and machine learning, using libraries such as NumPy and Pandas for data processing and modeling. 2. Python is concise and efficient in automation and scripting. 3. JavaScript is indispensable in front-end development and is used to build dynamic web pages and single-page applications. 4. JavaScript plays a role in back-end development through Node.js and supports full-stack development.

C and C play a vital role in the JavaScript engine, mainly used to implement interpreters and JIT compilers. 1) C is used to parse JavaScript source code and generate an abstract syntax tree. 2) C is responsible for generating and executing bytecode. 3) C implements the JIT compiler, optimizes and compiles hot-spot code at runtime, and significantly improves the execution efficiency of JavaScript.

JavaScript's application in the real world includes front-end and back-end development. 1) Display front-end applications by building a TODO list application, involving DOM operations and event processing. 2) Build RESTfulAPI through Node.js and Express to demonstrate back-end applications.

The main uses of JavaScript in web development include client interaction, form verification and asynchronous communication. 1) Dynamic content update and user interaction through DOM operations; 2) Client verification is carried out before the user submits data to improve the user experience; 3) Refreshless communication with the server is achieved through AJAX technology.

Understanding how JavaScript engine works internally is important to developers because it helps write more efficient code and understand performance bottlenecks and optimization strategies. 1) The engine's workflow includes three stages: parsing, compiling and execution; 2) During the execution process, the engine will perform dynamic optimization, such as inline cache and hidden classes; 3) Best practices include avoiding global variables, optimizing loops, using const and lets, and avoiding excessive use of closures.

Python is more suitable for beginners, with a smooth learning curve and concise syntax; JavaScript is suitable for front-end development, with a steep learning curve and flexible syntax. 1. Python syntax is intuitive and suitable for data science and back-end development. 2. JavaScript is flexible and widely used in front-end and server-side programming.

Python and JavaScript have their own advantages and disadvantages in terms of community, libraries and resources. 1) The Python community is friendly and suitable for beginners, but the front-end development resources are not as rich as JavaScript. 2) Python is powerful in data science and machine learning libraries, while JavaScript is better in front-end development libraries and frameworks. 3) Both have rich learning resources, but Python is suitable for starting with official documents, while JavaScript is better with MDNWebDocs. The choice should be based on project needs and personal interests.


Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

MantisBT
Mantis is an easy-to-deploy web-based defect tracking tool designed to aid in product defect tracking. It requires PHP, MySQL and a web server. Check out our demo and hosting services.

mPDF
mPDF is a PHP library that can generate PDF files from UTF-8 encoded HTML. The original author, Ian Back, wrote mPDF to output PDF files "on the fly" from his website and handle different languages. It is slower than original scripts like HTML2FPDF and produces larger files when using Unicode fonts, but supports CSS styles etc. and has a lot of enhancements. Supports almost all languages, including RTL (Arabic and Hebrew) and CJK (Chinese, Japanese and Korean). Supports nested block-level elements (such as P, DIV),

Dreamweaver CS6
Visual web development tools

DVWA
Damn Vulnerable Web App (DVWA) is a PHP/MySQL web application that is very vulnerable. Its main goals are to be an aid for security professionals to test their skills and tools in a legal environment, to help web developers better understand the process of securing web applications, and to help teachers/students teach/learn in a classroom environment Web application security. The goal of DVWA is to practice some of the most common web vulnerabilities through a simple and straightforward interface, with varying degrees of difficulty. Please note that this software

ZendStudio 13.5.1 Mac
Powerful PHP integrated development environment