<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>6_手写_将虚拟 Dom 转化为真实 Dom(类似的递归题-必考)</title></head><body><script>const VDOM = {tag: 'DIV',attrs: {id: 'app'},children: ['dd', {tag: 'SPAN',children: [{tag: 'A',children: ['aa']}]},{tag: 'SPAN',children: [{tag: 'A',children: ['bb']},{tag: 'A',children: ['cc']}]}]};// 渲染函数function _render(vnode) {// 若是数字类型转化为字符串,因为 document.createTextNode(vnode: string)if (typeof vnode === 'number') {vnode = String(vnode)}if (typeof vnode === 'string') {// 返回文本节点对象return document.createTextNode(vnode)}// 生成普通 DOMconst dom = document.createElement(vnode.tag)if (vnode.attrs) {Object.keys(vnode.attrs).forEach((key) => {const value = vnode.attrs[key]dom.setAttribute(key, value)})}// !重点 子数组进行递归操作vnode.children.forEach((child) => dom.appendChild(_render(child)))return dom};document.body.appendChild(_render(VDOM));/*!:类似面试题*/const el = {tagName: 'ul',children: ['dd',{tagName: 'li',props: {class: 'item'},children: ['aa']},{tagName: 'li',props: {class: 'item'},children: ['bb']},{tagName: 'li',props: {class: 'item'},children: ['cc']}]}function vDOM(ele) {if (Array.isArray(ele)) {const res = []ele.forEach(item => {res.push(vDOM(item))})// 注意:此出返回的是一个数组return res} else if (Object.prototype.toString.call(ele) === '[object Object]') {const {tagName,children} = eleconst tag = document.createElement(tagName)const child = vDOM(children)// 注意:需要将返回的数组进行解构,然后将数组中的 DOM 节点 append 到创建的 DOM 元素上。使用展开运算符...tag.append(...child)return tag} else {const dom = document.createTextNode(ele)return dom}}document.body.appendChild(vDOM(el));</script></body></html>
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>渲染器</title></head><body><script>const vnode = {tag: 'div',props: {id: 'root',onClick: () => alert('hello')},children: 'click me'};function renderer(vnode, container) {const el = document.createElement(vnode.tag)// 遍历 props 对象for (const key in vnode.props) {// 重点。。。。if (/^on/.test(key)) {el.addEventListener(key.substr(2).toLowerCase(), vnode.props[key])}else {el.setAttribute(key + '', vnode.props[key])}}// 处理 childrenif (typeof vnode.children === 'string') {el.appendChild(document.createTextNode(vnode.children))} else if (Array.isArray(vnode.children)) {vnode.children.forEach(child => renderer(child, el))}// 将元素添加到挂载节点下container.appendChild(el)}renderer(vnode, document.body);</script></body></html>
涉及到的知识点
append()appendChild()document.createElement()document.createTextNode()dom.setAttribute(key, value)Object.prototype.toString.call(ele) === '[object Object]'
append 和 appendChild 的区别
DOM 中有两个常用的方法:
- append
- appendChild
你知道它们的区别吗?先看 TypeScript 类型:
append(...nodes: (Node | string)[]): void;appendChild<T extends Node>(node: T): T;
参数类型不同
append可插入Node节点或字符串,但 appendChild 只能插入Node节点,例如:
- 插入节点对象 ```typescript const parent = document.createElement(‘div’) const child = document.createElement(‘p’)
parent.append(child) //
parent.appendChild(child) //
- 插入字符串```typescriptconst parent = document.createElement('div')parent.append('text') // <div>text</div>// Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'parent.appendChild('text')
参数个数不同
append可传入多个参数;但 appendChild只接受一个参数。
const parent = document.createElement('div')const child = document.createElement('p')const childTwo = document.createElement('p')// <div><p></p><p></p>Hello world</div>parent.append(child, childTwo, 'Hello world')// <div><p></p></div>parent.appendChild(child, childTwo, 'Hello world')
返回值不同
append没有返回值,但 appendChild返回插入的节点对象:
const parent = document.createElement('div')const child = document.createElement('p')const appendValue = parent.append(child)console.log(appendValue) // undefinedconst appendChildValue = parent.appendChild(child)console.log(appendChildValue) // <p><p>
