以下为ChatGPT自动生成的解答,正确与否请自行判断。
BFE.devAI解答 - JavaScript 代码题
113. Virtual DOM I
实现思路:
- 序列化:遍历DOM树,转换为对应的object literal格式
- 反序列化:遍历object literal,构建新的DOM树
实现序列化:virtualize()
根据React的“输出结果”,我们知道一个element可以转化为 {type, props}
的格式,props包含了元素的属性(props)和子元素(children)。
我们的实例输出的是一个JSX标签,JSX转换为element后的结构同上面描述的一样的。
因此,我们遍历HTML树即可得到一个对应的object literal表达:
function virtualize(node) {
if (node.nodeType === Node.TEXT_NODE) {
return node.textContent.trim();
} else { // 是element
const tagName = node.tagName.toLowerCase();
const children = Array.from(node.childNodes)
.map(child => virtualize(child));
const attrs = Array.from(node.attributes)
.reduce((obj, attr) => {
obj[attr.name] = attr.value;
return obj;
}, {});
return {
type: tagName,
props: { children, ...attrs },
}
}
}
遍历到文本节点时,直接返回文本内容;遍历到element节点时,则用tagName
作为对象的type值,属性值用childNodes
和attributes
得到。
示例输出的结果为:
{
"type": "div",
"props": {
"children": [
{
"type": "h1",
"props": {
"children": "this is"
}
},
{
"type": "p",
"props": {
"className": "paragraph",
"children": [
"a",
{
"type": "button",
"props": {
"children": "button"
}
},
"from",
{
"type": "a",
"props": {
"href": "https://bfe.dev",
"children": [
{
"type": "b",
"props": {
"children": "BFE"
}
},
".dev"
]
}
}
]
}
}
]
}
}
实现反序列化:render()
根据React的“输出结果”,我们知道一个element可以转化为 {type, props}
的格式,props包含了元素的属性(props)和子元素(children)。
因此,我们通过遍历object literal,在浏览器中构建一颗DOM树,HTML是以element的形式展示的,因此我们可以在createElement()
函数遍历时递归执行并返回标签。
function render(node) {
if(typeof node === 'string') {
return document.createTextNode(node);
}
const $el = document.createElement(node.type);
for(let [key, val] of Object.entries(node.props)) {
if(key === 'children') {
for(const child of val) {
$el.appendChild(render(child));
}
} else if (/^on/.test(key)) {
// 不处理事件处理程序
} else {
$el.setAttribute(key, val);
}
}
return $el;
}
完整实现
function virtualize(node) {
if (node.nodeType === Node.TEXT_NODE) {
return node.textContent.trim();
} else { // 是element
const tagName = node.tagName.toLowerCase();
const children = Array.from(node.childNodes)
.map(child => virtualize(child));
const attrs = Array.from(node.attributes)
.reduce((obj, attr) => {
obj[attr.name] = attr.value;
return obj;
}, {});
return {
type: tagName,
props: { children, ...attrs },
}
}
}
function render(node) {
if(typeof node === 'string') {
return document.createTextNode(node);
}
const $el = document.createElement(node.type);
for(let [key, val] of Object.entries(node.props)) {
if(key === 'children') {
for(const child of val) {
$el.appendChild(render(child));
}
} else if (/^on/.test(key)) {
// 不处理事件处理程序
} else {
$el.setAttribute(key, val);
}
}
return $el;
}
// test case
// <div>
// <h1> this is </h1>
// <p className="paragraph"> a <button> button </button> from <a href="https://bfe.dev"><b>BFE</b>.dev</a></p>
// </div>
const node = document.createElement('div')
node.innerHTML = '<h1> this is </h1><p className="paragraph"> a <button> button </button> from <a href="https://bfe.dev"><b>BFE</b>.dev</a></p>'
const el = virtualize(node);
const $root = document.querySelector('#root')
$root.appendChild(render(el));
总结
- 序列化:将DOM tree遍历转换为object literal格式
- 反序列化:遍历object literal,在浏览器中构建一颗DOM树
- 需要注意的是:自定义组件,事件处理函数等自定义操作不在考虑范围之内;只考虑标准HTML标签;属性名为camelCase形式。