分享一套自己封装的极简h()创建 DOM 工具函数,无第三方依赖,纯原生 JS 实现类 JSX 创建页面元素,同时兼容普通 HTML 标签、全部 SVG 标签,支持事件绑定、style 对象、dataset、class 数组、简易类组件渲染,附带基础调用示例,可直接在油猴脚本中使用。
脚本仅做前端 DOM 渲染学习演示,无网络请求、数据劫持、自动操作等功能,纯前端技术练习 Demo。
// ==UserScript==
// @name WebPlatform Lab V6
// @namespace https://docs.scriptcat.org/
// @version 0.1.0
// @description try to take over the world!
// @AuThor LiuQi
// @match https://*/*
// @grant none
// @noframes
// ==/UserScript==
(function () {
'use strict';
/**
* h 创建DOM
* @Param {string | Function} tag 标签名 自定义渲染
* @param {Object} props 自定义属性对象
* @param {...any} children 自定义子节点
* @returns {HTMLElement | SVGElement} 创建完成的DOM节点
* @author LiuQi
*/
function h(tag, props = {}, ...children) {
if (typeof tag === "function") { // 当前tag 标签 - 类组件
// 子节点扁平平铺过滤
const flatChildren = children.flat().filter(child => child != null && child !== false && child !== true);
// 优先使用children 无效节点则读取props.children
const effectiveChildren = flatChildren.length > 0 ? flatChildren : (props && props.children != null ? [].concat(props.children) : []);
// 创建Tag实例
const instance = new tag({ ...(props || {}), children: effectiveChildren.length === 1 ? effectiveChildren[0] : effectiveChildren });
// 调取render函数 获取DOM
const node = instance.render();
if (instance && !instance._mounted && instance.el) {// 若组件存在挂载钩子且未执行 自动执行 attach 挂载
instance._attach();
}
return node;
}
// SVG_TAGS SVG标签集合
const SVG_TAGS = new Set(["svg", "path", "circle", "rect", "line", "polyline", "polygon",
"ellipse", "g", "defs", "use", "linearGradient",
"radialGradient", "stop", "text", "tspan",
"mask", "pattern", "filter", "feGaussianBlur", "feOffset", "feMerge", "feMergeNode",
"clipPath", "image", "foreignObject", "symbol", "marker", "title", "desc"]);
// 当前tag 标签类型 在SVG标签集合使用SVG命名空间 否则直接创建DOM
const el = SVG_TAGS.has(tag) ? document.createElementNS("http://www.w3.org/2000/svg", tag) : document.createElement(tag);
for (const [key, value] of Object.entries(props || {})) { // 遍历 props属性
// value 直为空直接跳过
if (value === null || value === false) continue;
if (key === "class" || key === "className") { // key 是 class 或者 className
// value 如果是数组类型进行拼接 否则直接获取值
const cls = Array.isArray(value) ? value.filter(Boolean).join(" ") : value;
if (typeof window !== "undefined" && window.SVGElement && el instanceof window.SVGElement) { // SVG元素
// setAttribute 方式
el.setAttribute("class", cls);
} else {
// className 类名方式
el.className = cls;
}
} else if (key === "style" && typeof value === "object") { // key 是 style 并且value 是 object 类型
// 拷贝style 属性
Object.assign(el.style, value);
} else if (key == "dataset" && typeof value === "object") { // key 是 dataset 并且value 是 object 类型
// 拷贝dataset 属性
Object.assign(el.dataset, value);
} else if (key.startsWith("on") && typeof value === "function") { // key 以on开头 并且value 是 function 类型
// DOM 监听 自定义的事件并转小写
el.addEventListener(key.slice(2).toLowerCase(), value);
} else if (key === "html") { // key 是 html
// DOM 直接渲染
el.innerHTML = value;
} else if (key in el && key !== "list" && typeof el[key] !== "function") { // 原生 DOM 属性优先赋值,赋值失败降级使用 setAttribute
try {
el[key] = value;
} catch {
el.setAttribute(key, value);
}
} else {// 其他的统一使用 setAttribute
el.setAttribute(key, value);
}
}
for (const child of children.flat()) { // 子节点扁平化处理
// 子节点过滤处理
if (child == null || child === false || child === true) continue;
// DOM 节点直接插入 否则转为文本节点再插入
el.append(child.nodeType ? child : document.createTextNode(String(child)));
}
return el;
}
class Render {
text;
constructor() {
this.text = "<h1>123456 .......... </h1>";
}
render() {
return this.text;
}
}
const hEl = h("div", { html: "<h1>HJK</h1>" }, ["好好学习", " 天天向上"]);
console.log(hEl);
hEl.append("1234");
document.body.prepend(hEl);
// Your code here...
})();