探索原生JS的力量:自定义实现类似于React的useState功能

1.写在前面

本方案特别适合希望在历史遗留的原生JavaScript项目中实现简单轻量级数据驱动机制的开发者。无需引入任何框架或第三方库,即可按照此方法封装出类似于React中useState的功能,轻松为项目添加状态管理能力,既保持了项目的轻量性,又提升了开发效率

2.优势总结 ★ 了解

  1. 轻量级响应式系统
  • 无虚拟DOM:直接监听状态变化并更新真实DOM,避免虚拟DOM的diff计算开销

  • 精准更新:只有订阅了状态变化的DOM元素会更新(相比React的组件级重渲染更精确)

  1. 类React开发体验
  • 提供useState+setState的API设计,降低学习成本

  • 支持函数式更新:setState(prev => prev + 1)

  1. 状态不可变性
  • 自动深拷贝状态,避免意外修改

  • 每次更新都生成新状态,便于实现时间旅行调试

  1. 批量更新优化
  • batch()可合并多次更新为单次渲染

  • 避免频繁DOM操作导致的布局抖动

  1. 多实例隔离
  • 不同模块可以使用独立的状态实例,避免全局污染

3.单例模式 ★ 重要

单例模式效果展示

单例模式封装

javascript 复制代码
/**
 * 单例模式状态管理
 * 整个应用共享同一个状态实例
 */

// ==================== 深拷贝工具 ====================
function deepClone(obj, hash = new WeakMap()) {
    if (obj == null) return obj;
    if (typeof obj !== 'object') return obj;

    const constructor = obj.constructor;
    const specialTypes = ['Date', 'RegExp', 'Map', 'Set'];
    if (specialTypes.includes(constructor.name)) {
        return new constructor(obj);
    }

    if (hash.has(obj)) return hash.get(obj);

    const clone = Array.isArray(obj) 
        ? [] 
        : Object.create(Object.getPrototypeOf(obj));
    hash.set(obj, clone);

    [...Object.getOwnPropertySymbols(obj), ...Object.keys(obj)].forEach(key => {
        clone[key] = deepClone(obj[key], hash);
    });

    return clone;
}

// ==================== 核心实现 ====================
const subscribers = new Map();
let batchQueue = [];
let isBatching = false;

function batchNotify(proxy) {
    const callbacks = subscribers.get(proxy);
    if (!callbacks) return;

    Promise.resolve().then(() => {
        callbacks.forEach(cb => {
            try {
                cb(proxy.value);
            } catch (e) {
                console.error('回调执行失败:', e);
            }
        });
    });
}

export const useState = (initialState) => {
    if (typeof initialState === 'undefined') {
        throw new Error('初始状态不能为undefined');
    }

    const proxy = new Proxy({ value: deepClone(initialState) }, {
        set(target, key, value) {
            if (key !== 'value') return false;
            target[key] = deepClone(value);
            if (!isBatching) batchNotify(proxy);
            return true;
        }
    });

    return {
        get state() { return proxy.value; },
        setState: (updater) => {
            if (isBatching) {
                batchQueue.push({ proxy, updater });
            } else {
                proxy.value = typeof updater === 'function' 
                    ? updater(proxy.value) 
                    : updater;
            }
        },
        subscribe: (callback) => {
            if (typeof callback !== 'function') {
                throw new Error('回调必须是函数');
            }

            if (!subscribers.has(proxy)) {
                subscribers.set(proxy, new Set());
            }
            subscribers.get(proxy).add(callback);

            return () => {
                subscribers.get(proxy)?.delete(callback);
            };
        }
    };
};

export const batch = (callback) => {
    if (isBatching) return callback();

    isBatching = true;
    batchQueue = [];

    try {
        callback();

        const updatesByProxy = new Map();
        batchQueue.forEach(({ proxy, updater }) => {
            if (!updatesByProxy.has(proxy)) {
                updatesByProxy.set(proxy, []);
            }
            updatesByProxy.get(proxy).push(updater);
        });

        updatesByProxy.forEach((updaters, proxy) => {
            let state = proxy.value;
            updaters.forEach(updater => {
                state = typeof updater === 'function' 
                    ? updater(state) 
                    : updater;
                state = deepClone(state);
            });
            proxy.value = state;
            batchNotify(proxy);
        });
    } finally {
        isBatching = false;
        batchQueue = [];
    }
};

单例模式HTML测试

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>单例模式测试</title>
    <script type="module">
        import { useState, batch } from './singleton-state.js';

        const counter = useState(0);

        counter.subscribe(count => {
            document.getElementById('count').textContent = count;
            console.log('当前计数:', count);
        });

        // 普通更新
        document.getElementById('increment').addEventListener('click', () => {
            counter.setState(c => c + 1);
        });

        // 批量更新
        document.getElementById('increment-5').addEventListener('click', () => {
            batch(() => {
                counter.setState(c => c + 1);
                counter.setState(c => c + 1);
                counter.setState(c => c + 1);
                counter.setState(c => c + 1);
                counter.setState(c => c + 1);
            });
        });
    </script>
</head>
<body>
<h1>单例模式测试</h1>
<div>计数: <span id="count">0</span></div>
<button id="increment">+1</button>
<button id="increment-5">+5 (批量)</button>
</body>
</html>

4.多例模式 ★ 重要·推荐

双例模式效果展示

双例模式封装

javascript 复制代码
/**
 * 多例模式状态管理工具
 * 允许创建多个独立的状态管理实例,每个实例拥有独立的状态和订阅系统
 * 
 * 主要特点:
 * 1. 可创建多个隔离的状态管理实例
 * 2. 每个实例拥有独立的useState和batch方法
 * 3. 完整的状态不可变性保证
 * 4. 支持批量更新优化性能
 * 
 * 使用方式:
 * const store = createStateStore();
 * const counter = store.useState(0);
 * 
 * counter.subscribe(state => console.log(state));
 * counter.setState(prev => prev + 1);
 * store.batch(() => { ... });
 */

// ==================== 深拷贝工具函数 ====================

/**
 * 高性能深拷贝函数
 * @param {any} obj - 需要拷贝的对象
 * @param {WeakMap} [hash=new WeakMap()] - 用于存储已拷贝对象的WeakMap(防止循环引用)
 * @returns {any} 深拷贝后的对象
 * 
 * 实现特点:
 * 1. 处理基本数据类型:直接返回
 * 2. 处理循环引用:使用WeakMap缓存已拷贝对象
 * 3. 保留特殊对象类型:Date、RegExp等
 * 4. 原型链继承:保持原型链关系
 * 5. 性能优化:使用Object.keys+Symbol属性遍历
 */
function deepClone(obj, hash = new WeakMap()) {
    // 处理null和undefined
    if (obj == null) return obj;
    
    // 处理基本数据类型(string, number, boolean, symbol, bigint)
    if (typeof obj !== 'object') return obj;
    
    // 处理特殊对象类型
    const constructor = obj.constructor;
    const specialTypes = ['Date', 'RegExp', 'Map', 'Set', 'WeakMap', 'WeakSet'];
    if (specialTypes.includes(constructor.name)) {
        return new constructor(obj);
    }
    
    // 检查循环引用
    if (hash.has(obj)) return hash.get(obj);
    
    // 根据对象类型创建空对象或数组
    const clone = Array.isArray(obj) 
        ? [] 
        : Object.create(Object.getPrototypeOf(obj));
    
    // 缓存当前对象,防止循环引用
    hash.set(obj, clone);
    
    // 拷贝Symbol类型属性
    const symKeys = Object.getOwnPropertySymbols(obj);
    if (symKeys.length > 0) {
        symKeys.forEach(symKey => {
            clone[symKey] = deepClone(obj[symKey], hash);
        });
    }
    
    // 拷贝普通属性
    Object.keys(obj).forEach(key => {
        clone[key] = deepClone(obj[key], hash);
    });
    
    return clone;
}

// ==================== 状态管理工厂函数 ====================

/**
 * 创建新的状态管理实例
 * @returns {Object} 包含useState和batch方法的对象
 * 
 * 每个实例包含:
 * 1. 独立的订阅者系统
 * 2. 独立的批量更新队列
 * 3. 独立的状态树
 */
export function createStateStore() {
    /**
     * 订阅者集合
     * Map结构:
     * key: 状态代理对象(Proxy)
     * value: 该状态的订阅者回调集合(Set)
     */
    const subscribers = new Map();
    
    /**
     * 批量更新队列
     * 数组结构,每个元素包含:
     * - proxy: 状态代理对象
     * - updater: 更新函数或值
     */
    let batchQueue = [];
    
    /**
     * 批量更新标志
     * @type {boolean}
     */
    let isBatching = false;
    
    // ==================== 内部工具方法 ====================
    
    /**
     * 通知订阅者状态变更
     * @param {Proxy} proxy - 状态代理对象
     * 
     * 实现特点:
     * 1. 使用微任务(Promise)异步执行通知
     * 2. 错误处理避免影响其他订阅者
     * 3. 自动清理无效订阅
     */
    function batchNotify(proxy) {
        // 获取当前状态的所有订阅者
        const callbacks = subscribers.get(proxy);
        if (!callbacks || callbacks.size === 0) return;
        
        // 使用微任务异步执行通知
        Promise.resolve().then(() => {
            // 获取当前状态值
            const state = proxy.value;
            
            // 遍历执行所有订阅回调
            callbacks.forEach(callback => {
                try {
                    callback(state);
                } catch (error) {
                    console.error('[状态通知错误] 订阅回调执行失败:', error);
                }
            });
        });
    }
    
    // ==================== 公开API ====================
    
    /**
     * 创建响应式状态
     * @param {any} initialState - 初始状态
     * @returns {Object} 包含state、setState和subscribe方法的对象
     * 
     * @throws {Error} 当initialState为undefined时抛出错误
     */
    function useState(initialState) {
        // 参数校验
        if (typeof initialState === 'undefined') {
            throw new Error('useState: 初始状态不能为undefined');
        }
        
        // 创建响应式代理对象
        const proxy = new Proxy(
            { value: deepClone(initialState) },
            {
                /**
                 * 代理set陷阱
                 * @param {Object} target - 目标对象
                 * @param {string} key - 属性名
                 * @param {any} value - 新值
                 * @returns {boolean} 是否设置成功
                 */
                set(target, key, value) {
                    // 只处理value属性的变更
                    if (key !== 'value') return false;
                    
                    // 深拷贝新值,确保状态不可变
                    target[key] = deepClone(value);
                    
                    // 非批量模式下立即通知订阅者
                    if (!isBatching) {
                        batchNotify(proxy);
                    }
                    
                    return true;
                }
            }
        );
        
        /**
         * 订阅状态变更
         * @param {Function} callback - 状态变更回调函数
         * @returns {Function} 取消订阅的函数
         * 
         * @throws {Error} 当callback不是函数时抛出错误
         */
        function subscribe(callback) {
            // 参数校验
            if (typeof callback !== 'function') {
                throw new Error('subscribe: 回调必须是函数');
            }
            
            // 初始化该状态的订阅者集合
            if (!subscribers.has(proxy)) {
                subscribers.set(proxy, new Set());
            }
            
            // 添加订阅者
            const callbacks = subscribers.get(proxy);
            callbacks.add(callback);
            
            // 返回取消订阅函数
            return function unsubscribe() {
                callbacks.delete(callback);
                
                // 清理空订阅集合
                if (callbacks.size === 0) {
                    subscribers.delete(proxy);
                }
            };
        }
        
        /**
         * 更新状态
         * @param {Function|any} updater - 更新函数或新状态值
         * 
         * 更新规则:
         * 1. 如果是函数:updater(prevState) => newState
         * 2. 如果是值:直接替换状态
         */
        function setState(updater) {
            if (isBatching) {
                // 批量模式下将更新操作加入队列
                batchQueue.push({
                    proxy,
                    updater
                });
            } else {
                // 直接更新模式
                proxy.value = typeof updater === 'function'
                    ? updater(proxy.value)
                    : updater;
            }
        }
        
        // 返回状态访问接口
        return {
            /**
             * 获取当前状态值
             * @returns {any} 当前状态
             */
            get state() {
                return proxy.value;
            },
            setState,
            subscribe
        };
    }
    
    /**
     * 批量更新状态
     * @param {Function} callback - 包含多个状态更新的回调函数
     * 
     * 实现特点:
     * 1. 合并多个setState调用为一次更新
     * 2. 自动处理状态依赖关系
     * 3. 最终只触发一次订阅通知
     */
    function batch(callback) {
        // 如果已经在批量模式中,直接执行回调
        if (isBatching) {
            callback();
            return;
        }
        
        // 进入批量模式
        isBatching = true;
        batchQueue = [];
        
        try {
            // 执行用户回调,收集所有setState操作
            callback();
            
            // 按状态代理分组更新操作
            const updatesByProxy = new Map();
            batchQueue.forEach(({ proxy, updater }) => {
                if (!updatesByProxy.has(proxy)) {
                    updatesByProxy.set(proxy, []);
                }
                updatesByProxy.get(proxy).push(updater);
            });
            
            // 处理每个状态代理的更新
            updatesByProxy.forEach((updaters, proxy) => {
                let currentState = proxy.value;
                
                // 按顺序应用所有更新器
                updaters.forEach(updater => {
                    currentState = typeof updater === 'function'
                        ? updater(currentState)
                        : updater;
                    
                    // 确保每次更新都是不可变的
                    currentState = deepClone(currentState);
                });
                
                // 最终更新状态值
                proxy.value = currentState;
                
                // 通知该状态的订阅者
                batchNotify(proxy);
            });
        } finally {
            // 确保无论是否出错都退出批量模式
            isBatching = false;
            batchQueue = [];
        }
    }
    
    // 返回实例方法
    return { useState, batch };
}

// ==================== 可选默认实例 ====================

/**
 * 默认导出的状态管理实例
 * 为方便使用,同时提供创建新实例和默认实例两种方式
 */
const defaultStore = createStateStore();
export const { useState: defaultUseState, batch: defaultBatch } = defaultStore;

双例模式HTML测试

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>多例模式状态管理测试</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
        }
        .counter {
            margin: 20px 0;
            padding: 15px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        button {
            padding: 8px 16px;
            margin-right: 10px;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <h1>多例模式状态管理测试</h1>
    
    <div class="counter">
        <h2>独立计数器1</h2>
        <div>当前值: <span id="counter1-value">0</span></div>
        <button id="counter1-increment">增加</button>
        <button id="counter1-batch">批量增加(+3)</button>
    </div>
    
    <div class="counter">
        <h2>独立计数器2</h2>
        <div>当前值: <span id="counter2-value">100</span></div>
        <button id="counter2-increment">增加</button>
    </div>
    
    <script type="module">
        // 从模块导入创建方法
        import { createStateStore } from './multi-state.js';
        
        // 创建两个完全独立的状态管理实例
        const store1 = createStateStore();
        const store2 = createStateStore();
        
        // 实例1:计数器1
        const counter1 = store1.useState(0);
        counter1.subscribe(state => {
            document.getElementById('counter1-value').textContent = state;
            console.log('计数器1更新:', state);
        });
        
        document.getElementById('counter1-increment').addEventListener('click', () => {
            counter1.setState(prev => prev + 1);
        });
        
        document.getElementById('counter1-batch').addEventListener('click', () => {
            store1.batch(() => {
                counter1.setState(prev => prev + 1);
                counter1.setState(prev => prev + 1);
                counter1.setState(prev => prev + 1);
            });
        });
        
        // 实例2:计数器2 (完全独立)
        const counter2 = store2.useState(100);
        counter2.subscribe(state => {
            document.getElementById('counter2-value').textContent = state;
            console.log('计数器2更新:', state);
        });
        
        document.getElementById('counter2-increment').addEventListener('click', () => {
            counter2.setState(prev => prev + 10);
        });
        
        // 暴露到全局方便测试
        window.stores = { store1, store2, counter1, counter2 };
    </script>
    
    <div style="margin-top: 30px; color: #666;">
        <h3>测试说明:</h3>
        <p>1. 两个计数器使用完全独立的状态管理实例</p>
        <p>2. 打开控制台可以查看状态变化日志</p>
        <p>3. 在控制台输入 <code>stores</code> 可以访问状态实例</p>
    </div>
</body>
</html>

5.单例模式和双例模式的区别 ★ 了解

  1. 单例模式

    • 全局共享一个状态树

    • 直接导出 useStatebatch

    • 适合中小型应用

  2. 多例模式

    • 通过 createStateStore() 创建独立实例

    • 每个实例有自己的状态和订阅系统

    • 适合大型应用或需要隔离状态的场景


6.兼容性分析 ★ 了解

  1. 支持的浏览器
特性 最低支持版本 覆盖率
Proxy Chrome 49+ ~98%
Firefox 18+
Edge 12+
Safari 10+
WeakMap IE 11+ ~99%
Promise (微任务) ES6+ ~98%
  1. 不兼容场景
  • IE 11及以下 :不支持Proxy(可用Object.defineProperty降级)

  • 老旧移动浏览器:部分Android 4.x设备不支持ES6

  1. Polyfill方案
javascript 复制代码
// 在入口文件添加以下polyfill
import 'core-js/stable';  // 提供Promise/WeakMap等
import 'proxy-polyfill';  // 提供Proxy的简单实现

7.全代码测试页 ★ 了解·测试

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>高性能响应式状态管理</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
            line-height: 1.6;
        }
        button {
            padding: 8px 16px;
            margin: 5px;
            cursor: pointer;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 4px;
        }
        button:hover {
            background-color: #45a049;
        }
        .container {
            margin: 20px 0;
            padding: 15px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        pre {
            background-color: #f5f5f5;
            padding: 10px;
            border-radius: 4px;
            overflow-x: auto;
        }
        .perf-info {
            color: #666;
            font-size: 0.9em;
            margin-top: 5px;
        }
    </style>
</head>
<body>
<h1>高性能响应式状态管理</h1>

<!-- 计数器容器 -->
<div class="container">
    <h2>性能计数器</h2>
    <div>当前值: <span id="counter-value">0</span></div>
    <div class="perf-info" id="counter-perf"></div>
    <button id="increment">增加 (+1)</button>
    <button id="increment-100">快速增加100次</button>
    <button id="increment-1000">压力测试1000次</button>
</div>

<!-- 用户信息容器 -->
<div class="container">
    <h2>用户信息</h2>
    <pre id="user-info"></pre>
    <div class="perf-info" id="user-perf"></div>
    <button id="update-user">更新用户信息</button>
</div>

<!-- 待办事项容器 -->
<div class="container">
    <h2>待办事项</h2>
    <ul id="todo-list"></ul>
    <input type="text" id="new-todo" placeholder="输入新事项">
    <button id="add-todo">添加</button>
    <div class="perf-info" id="todo-perf"></div>
</div>

<script>
    /**
     * 高性能深拷贝函数
     * 特点:
     * 1. 处理循环引用
     * 2. 保留特殊对象类型(Date, RegExp等)
     * 3. 惰性拷贝(按需拷贝)
     * @param {any} obj - 需要拷贝的对象
     * @param {WeakMap} hash - 用于存储已拷贝对象的WeakMap(防止循环引用)
     * @return {any} 深拷贝后的对象
     */
    function deepClone(obj, hash = new WeakMap()) {
        // 基本类型直接返回
        if (obj === null || typeof obj !== 'object') return obj;

        // 处理循环引用
        if (hash.has(obj)) return hash.get(obj);

        // 处理特殊对象
        const constructor = obj.constructor;
        if (/^(Date|RegExp|Map|Set)$/i.test(constructor.name)) {
            return new constructor(obj);
        }

        // 初始化克隆对象
        const clone = Array.isArray(obj) ? [] : Object.create(Object.getPrototypeOf(obj));
        hash.set(obj, clone);

        // 使用Object.keys+forEach比for-in性能更好
        Object.keys(obj).forEach(key => {
            clone[key] = deepClone(obj[key], hash);
        });

        return clone;
    }

    /**
     * 创建高性能状态管理Store
     * @return {Object} 包含useState和batch方法的对象
     */
    function createStore() {
        const subscribers = new Map(); // 存储订阅者回调函数
        let batchQueue = []; // 批量更新队列
        let isBatching = false; // 是否处于批量更新模式

        /**
         * 批量执行回调(使用微任务节流)
         * @param {Proxy} proxy - 状态代理对象
         */
        function batchNotify(proxy) {
            const callbacks = subscribers.get(proxy);
            if (!callbacks) return;

            // 使用微任务确保在UI更新前处理所有状态变更
            Promise.resolve().then(() => {
                const state = proxy.value;
                callbacks.forEach(callback => {
                    try {
                        callback(state);
                    } catch (e) {
                        console.error('订阅回调出错:', e);
                    }
                });
            });
        }

        /**
         * 创建响应式状态
         * @param {any} initialState - 初始状态
         * @return {Object} 包含state、setState和subscribe方法的对象
         */
        function useState(initialState) {
            // 验证初始状态
            if (typeof initialState === 'undefined') {
                throw new Error('Initial state cannot be undefined');
            }

            // 创建响应式代理
            const proxy = new Proxy({ value: deepClone(initialState) }, {
                set(target, key, value) {
                    if (key !== 'value') return false;

                    // 深拷贝新值
                    target[key] = deepClone(value);

                    // 非批量模式下立即通知
                    if (!isBatching) {
                        batchNotify(proxy);
                    }

                    return true;
                }
            });

            /**
             * 订阅状态变化
             * @param {Function} callback - 状态变化时的回调函数
             * @return {Function} 取消订阅的函数
             */
            function subscribe(callback) {
                if (typeof callback !== 'function') {
                    throw new Error('订阅回调必须是一个函数');
                }

                if (!subscribers.has(proxy)) {
                    subscribers.set(proxy, new Set());
                }

                const callbacks = subscribers.get(proxy);
                callbacks.add(callback);

                // 返回取消订阅函数
                return () => {
                    callbacks.delete(callback);
                    if (callbacks.size === 0) {
                        subscribers.delete(proxy);
                    }
                };
            }

            /**
             * 更新状态
             * @param {Function|any} updater - 更新函数或新状态
             */
            function setState(updater) {
                if (isBatching) {
                    // 批量模式下将更新器和代理存入队列
                    batchQueue.push({
                        proxy,
                        updater
                    });
                } else {
                    // 直接更新
                    proxy.value = typeof updater === 'function'
                        ? updater(proxy.value)
                        : updater;
                }
            }

            return {
                get state() { return proxy.value; }, // 获取当前状态
                setState, // 更新状态方法
                subscribe // 订阅状态变化方法
            };
        }

        /**
         * 批量更新
         * @param {Function} callback - 包含多个状态更新的回调函数
         */
        function batch(callback) {
            isBatching = true;
            batchQueue = []; // 清空队列
            try {
                callback(); // 执行回调,收集所有setState调用

                // 将更新按对应的状态代理分组
                const updatesByProxy = new Map();
                batchQueue.forEach(({ proxy, updater }) => {
                    if (!updatesByProxy.has(proxy)) {
                        updatesByProxy.set(proxy, []);
                    }
                    updatesByProxy.get(proxy).push(updater);
                });

                // 处理每个代理的更新队列
                updatesByProxy.forEach((updaters, proxy) => {
                    let currentState = proxy.value;
                    // 按顺序应用所有更新器函数
                    updaters.forEach(updater => {
                        currentState = typeof updater === 'function'
                            ? updater(currentState) // 基于当前状态计算新值
                            : updater;
                        currentState = deepClone(currentState); // 深拷贝新状态
                    });
                    // 一次性更新代理的值
                    proxy.value = currentState;
                    // 手动触发通知
                    batchNotify(proxy);
                });

            } finally {
                isBatching = false;
                batchQueue = []; // 清空队列
            }
        }

        return { useState, batch };
    }

    // ==================== 使用示例 ====================
    const { useState, batch } = createStore();

    /**
     * 性能监控工具
     * @param {string} name - 监控器名称
     * @return {Object} 包含record和updateUI方法的对象
     */
    function createPerfMonitor(name) {
        let lastTime = performance.now();
        let count = 0;
        let totalTime = 0;

        return {
            /**
             * 记录性能数据
             * @return {Object} 包含duration、avg和count的性能数据
             */
            record() {
                const now = performance.now();
                const duration = now - lastTime;
                lastTime = now;
                count++;
                totalTime += duration;

                return {
                    duration: duration.toFixed(2),
                    avg: (totalTime / count).toFixed(2),
                    count
                };
            },

            /**
             * 更新UI显示性能数据
             * @param {string} elementId - 显示性能数据的元素ID
             */
            updateUI(elementId) {
                const perf = this.record();
                document.getElementById(elementId).textContent =
                    `最近更新: ${perf.duration}ms | 平均: ${perf.avg}ms | 更新次数: ${perf.count}`;
            }
        };
    }

    // 1. 计数器状态
    const counter = useState(0);
    const counterPerf = createPerfMonitor('counter');

    // 订阅计数器状态变化
    counter.subscribe(count => {
        document.getElementById('counter-value').textContent = count;
        counterPerf.updateUI('counter-perf');
    });

    // 2. 用户信息状态
    const user = useState({
        name: '张三',
        age: 25,
        address: {
            city: '北京',
            street: '朝阳区'
        },
        createdAt: new Date()
    });

    const userPerf = createPerfMonitor('user');
    // 订阅用户信息状态变化
    user.subscribe(user => {
        document.getElementById('user-info').textContent = JSON.stringify(user, null, 2);
        userPerf.updateUI('user-perf');
    });

    // 3. 待办事项状态
    const todos = useState([]);
    const todoPerf = createPerfMonitor('todo');

    // 订阅待办事项状态变化
    todos.subscribe(todos => {
        const listElement = document.getElementById('todo-list');
        listElement.innerHTML = '';

        todos.forEach((todo, index) => {
            const li = document.createElement('li');
            li.textContent = `${index + 1}. ${todo.text}`;
            if (todo.completed) {
                li.style.textDecoration = 'line-through';
                li.style.color = '#888';
            }

            // 添加完成/取消按钮
            const completeButton = document.createElement('button');
            completeButton.textContent = todo.completed ? '取消' : '完成';
            completeButton.style.marginLeft = '10px';
            completeButton.onclick = () => {
                todos.setState(prev => {
                    const newTodos = [...prev];
                    newTodos[index] = {
                        ...newTodos[index],
                        completed: !newTodos[index].completed
                    };
                    return newTodos;
                });
            };

            li.appendChild(completeButton);
            listElement.appendChild(li);
        });

        todoPerf.updateUI('todo-perf');
    });

    // ==================== 事件绑定 ====================
    // 计数器操作
    document.getElementById('increment').addEventListener('click', () => {
        counter.setState(c => c + 1);
    });

    document.getElementById('increment-100').addEventListener('click', () => {
        batch(() => {
            for (let i = 0; i < 100; i++) {
                counter.setState(c => c + 1);
            }
        });
    });

    document.getElementById('increment-1000').addEventListener('click', () => {
        batch(() => {
            for (let i = 0; i < 1000; i++) {
                counter.setState(c => c + 1);
            }
        });
    });

    // 用户信息操作
    document.getElementById('update-user').addEventListener('click', () => {
        user.setState(prev => ({
            ...prev,
            age: prev.age + 1,
            address: {
                ...prev.address,
                street: `朝阳区${Math.floor(Math.random() * 100)}号`
            },
            updatedAt: new Date()
        }));
    });

    // 待办事项操作
    document.getElementById('add-todo').addEventListener('click', () => {
        const input = document.getElementById('new-todo');
        const text = input.value.trim();

        if (text) {
            todos.setState(prev => [
                ...prev,
                { text, completed: false }
            ]);
            input.value = '';
        }
    });

    // 初始化输入框回车事件
    document.getElementById('new-todo').addEventListener('keypress', (e) => {
        if (e.key === 'Enter') {
            document.getElementById('add-todo').click();
        }
    });
</script>
</body>
</html>
相关推荐
天天扭码几秒前
一分钟吃透一道面试算法题——字母异位词分组(最优解)
前端·javascript·算法
天天扭码12 分钟前
JavaScript 中字符串转字符数组的两种优雅方式
前端·javascript·代码规范
何遇er14 分钟前
在 AI 编程的热潮下对低代码的思考
前端·低代码·ai编程
何遇er17 分钟前
一句 Prompt 自动生成表单:我在低代码平台里是怎么接入生成式 AI 的
前端·低代码·ai编程
_一条咸鱼_19 分钟前
Vue 指令模块深度剖析:从基础应用到源码级解析(十二)
前端·javascript·面试
薯条不要番茄酱27 分钟前
【JavaEE初阶】多线程重点知识以及常考的面试题-多线程进阶(一)
java·前端·java-ee
只会安静敲代码的 小周1 小时前
uniapp上传图片时(可选微信头像、相册、拍照)
前端·微信·uni-app
kovlistudio1 小时前
红宝书第四十六讲:Node.js基础与API设计解析
前端·javascript·node.js
陈哥聊测试1 小时前
这款自研底层框架,你说不定已经用上了
前端·后端·开源
m0_zj1 小时前
41.[前端开发-JavaScript高级]Day06-原型关系图-ES6类的使用-ES6转ES5
开发语言·javascript·es6