吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 164|回复: 2
上一主题 下一主题
收起左侧

[学习记录] 手写极简 JS 虚拟 DOM 创建工具 h 函数|兼容 SVG / 事件 / 样式 / 类组件油猴 Demo

[复制链接]
跳转到指定楼层
楼主
LIUQIA 发表于 2026-7-29 13:34 回帖奖励

分享一套自己封装的极简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...
})();

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
fqhs 发表于 2026-8-1 09:51
大佬这个只是前端渲染的一个效果的代码吗?
3#
 楼主| LIUQIA 发表于 2026-8-1 14:09 |楼主
fqhs 发表于 2026-8-1 09:51
大佬这个只是前端渲染的一个效果的代码吗?

这是 一个渲染 DOM的工具封装方法 可以根据我判断的类型 传入不同参数 会渲染DOM树
也就是 动态HTML
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - 52pojie.cn ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2026-8-25 16:06

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表