Author: Siddharth
Translator: Frontend Xiaozhi
Source: dev
If you have dreams and dry goods, search on [Moving to the World] Follow this brushing wit who is still doing dishes in the early morning.
This article GitHub https://github.com/qq449245884/xiaozhi has been included, the first-line interview complete test site, information and my series of articles.
You may have heard of Virtual DOM (and Shadow DOM). Maybe even used it (JSX is basically the syntactic sugar of VDOM). If you want to learn more, then take a look at this article today.
What is virtual DOM?
DOM manipulation is expensive. When done once, the difference may seem small (about 0.4 milliseconds difference between assigning an attribute to an object), but it will increase over time.
// 将属性赋值给对象1000次
let obj = {};
console.time("obj");
for (let i = 0; i < 1000; i++) {
obj[i] = i;
}
console.timeEnd("obj");
// 操纵dom 1000次
console.time("dom");
for (let i = 0; i < 1000; i++) {
document.querySelector(".some-element").innerHTML += i;
}
console.timeEnd("dom");
When I run the above code snippet, I found that the first loop took about 3ms , and the second loop took about 41ms .
Let us give a more real example.
function generateList(list) {
let ul = document.createElement('ul');
document.getElementByClassName('.fruits').appendChild(ul);
list.forEach(function (item) {
let li = document.createElement('li');
ul.appendChild(li);
li.innerHTML += item;
});
return ul;
}
document.querySelector("ul.some-selector").innerHTML = generateList(["Banana", "Apple", "Orange"])
So far, everything is fine. Now, if the array changes, we need to re-render, we do this:
document.querySelector("ul.some-selector").innerHTML = generateList(["Banana", "Apple", "Mango"])
See what went wrong?
Even if only one element needs to be changed, we will change the entire element because we are lazy.
This is why the virtual DOM was created. So what is a virtual dom?
Virtual DOM is the representation of DOM as an object. Suppose we have the following HTML:
<div class="contents">
<p>Text here</p>
<p>Some other <b>Bold</b> content</p>
</div>
It can be written as the following VDOM objects:
let vdom = {
tag: "div",
props: { class: 'contents' },
children: [
{
tag: "p",
children: "Text here"
},
{
tag: "p",
children: ["Some other ", { tag: "b", children: "Bold" }, " content"]
}
]
}
Please note that there may be more attributes in actual development, this is a simplified version.
VDOM is an object with:
- An attribute called tag (sometimes called type), which represents the name of the tag
- A
props
, which contains all props - If the content is only text, it is a string
- If the content contains elements, the vdom array
We use VDOM like this:
- We changed vdom instead of dom
- The function checks all the differences between DOM and VDOM and only changes the changed part
- Changes to the VDOM are marked as the latest change, so that we can save more time next time we compare the VDOM.
what is the benefit?
Knowing what VDOM is, let's improve the previous generateList
function.
function generateList(list) {
// VDOM 生成过程,待下补上
}
patch(oldUL, generateList(["Banana", "Apple", "Orange"]));
Don't mind the patch
function, its role is to append the changed part to the DOM. When changing the DOM later:
patch(oldUL, generateList(["Banana", "Apple", "Mango"]));
patch
function finds that only the third li
has changed, not all three elements have changed, so only the third li element will be operated.
Build VDOM!
We need to do 4 things:
- Create a virtual node (vnode)
- Mount VDOM
- Uninstall VDOM
- Patch (compare two vnodes, find the differences, and then mount)
Create vnode
function createVNode(tag, props = {}, children = []) {
return { tag, props, children}
}
In Vue (and many other places), this function is called h
, short for hyperscript.
Mount VDOM
By mounting, attach the vnode to any container, such as #app
or any other place where it should be mounted.
This function will recursively traverse the child nodes of all nodes and mount them in their respective containers.
Note that all the code below is placed in the mount function.
function mount(vnode, container) { ... }
Create DOM element
const element = (vnode.element = document.createElement(vnode.tag))
You might wonder vnode.element
is. It is just an internally set attribute, and we can know which element is the parent element vnode
Set all properties from the props
We can loop them
Object.entries(vnode.props || {}).forEach([key, value] => {
element.setAttribute(key, value)
})
There are two situations that need to be handled when mounting sub-elements:
- children are just text
- children is an array of vnode
if (typeof vnode.children === 'string') {
element.textContent = vnode.children
} else {
vnode.children.forEach(child => {
mount(child, element) // 递归挂载子节点
})
}
Finally, we must add the content to the DOM:
container.appendChild(element)
final result:
function mount(vnode, container) {
const element = (vnode.element = document.createElement(vnode.tag))
Object.entries(vnode.props || {}).forEach([key, value] => {
element.setAttribute(key, value)
})
if (typeof vnode.children === 'string') {
element.textContent = vnode.children
} else {
vnode.children.forEach(child => {
mount(child, element) // Recursively mount the children
})
}
container.appendChild(element)
}
Uninstall vnode
Uninstallation is as simple as removing an element from the DOM:
function unmount(vnode) {
vnode.element.parentNode.removeChild(vnode.element)
}
patch vnode.
This is the (relatively speaking) most complex function we have to write. The thing to do is to find the difference between the two vnodes and only patch the changed part.
function patch(VNode1, VNode2) {
// 指定父级元素
const element = (VNode2.element = VNode1.element);
// 现在我们要检查两个vnode之间的区别
// 如果节点具有不同的标记,则说明整个内容已经更改。
if (VNode1.tag !== VNode2.tag) {
// 只需卸载旧节点并挂载新节点
mount(VNode2, element.parentNode)
unmount(Vnode1)
} else {
// 节点具有相同的标签
// 所以我们要检查两个部分
// - Props
// - Children
// 这里不打算检查 Props,因为它会增加代码的复杂性,我们先来看怎么检查 Children 就行啦
// 检查 Children
// 如果新节点的 children 是字符串
if (typeof VNode2.children == "string") {
// 如果两个孩子完全不同
if (VNode2.children !== VNode1.children) {
element.textContent = VNode2.children;
}
} else {
// 如果新节点的 children 是一个数组
// - children 的长度是一样的
// - 旧节点比新节点有更多的子节点
// - 新节点比旧节点有更多的子节点
// 检查长度
const children1 = VNode1.children;
const children2 = VNode2.children;
const commonLen = Math.min(children1.length, children2.length)
// 递归地调用所有公共子节点的patch
for (let i = 0; i < commonLen; i++) {
patch(children1[i], children2[i])
}
// 如果新节点的children 比旧节点的少
if (children1.length > children2.length) {
children1.slice(children2.length).forEach(child => {
unmount(child)
})
}
// 如果新节点的children 比旧节点的多
if (children2.length > children1.length) {
children2.slice(children1.length).forEach(child => {
mount(child, element)
})
}
}
}
}
This is a basic version implemented by vdom, which is convenient for us to quickly grasp this concept. Of course there are still some things to do, including checking props and some performance improvements.
Now let's render a vdom!
Back to the generateList
example. For our vdom implementation, we can do
function generateList(list) {
let children = list.map(child => createVNode("li", null, child));
return createVNode("ul", { class: 'fruits-ul' }, children)
}
mount(generateList(["apple", "banana", "orange"]), document.querySelector("#app")/* any selector */)
Online example: https://codepen.io/SiddharthShyniben/pen/MWpQrwM
~End, I’m Xiaozhi. Let’s go SPA, see you next time!
code is deployed, the possible bugs cannot be known in real time. In order to solve these bugs afterwards, a lot of time was spent on log debugging. By the way, we recommend a useful BUG monitoring tool Fundebug .
Original: https://dev.to/siddharthshyniben/what-is-the-virtual-dom-let-s-build-it-5070
communicate with
There are dreams and dry goods. WeChat search [Moving to the World] Follow this brushing wit who is still doing dishes in the early morning.
This article GitHub https://github.com/qq449245884/xiaozhi has been included, the first-line interview complete test site, information and my series of articles.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。