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>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #1a2a6c, #b21f1f, #fdbb2d);
color: #fff;
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1800px;
margin: 0 auto;
}
header {
text-align: center;
padding: 20px 0;
margin-bottom: 20px;
}
h1 {
font-size: 2.2rem;
margin-bottom: 10px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
}
.subtitle {
font-size: 1.1rem;
opacity: 0.9;
max-width: 800px;
margin: 0 auto;
line-height: 1.6;
}
.main-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.panel {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 15px;
padding: 20px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.panel h2 {
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
display: flex;
align-items: center;
}
.panel h2 .icon {
margin-right: 10px;
font-size: 1.4rem;
}
.controls {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 20px;
}
button {
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
padding: 10px 15px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 500;
display: flex;
align-items: center;
gap: 8px;
}
button:hover {
background: rgba(255, 255, 255, 0.3);
transform: translateY(-2px);
}
button:active {
transform: translateY(0);
}
.speed-controls {
display: flex;
align-items: center;
gap: 10px;
margin-top: 15px;
}
.speed-controls label {
font-weight: 500;
}
.speed-controls input {
width: 150px;
}
.code-editor {
background: rgba(0, 0, 0, 0.4);
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
height: 100%;
display: flex;
flex-direction: column;
}
.code-input {
width: 100%;
flex: 1;
background: rgba(0, 0, 0, 0.5);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 5px;
padding: 15px;
font-family: 'Courier New', monospace;
resize: none;
margin-bottom: 15px;
font-size: 1rem;
line-height: 1.5;
}
.code-controls {
display: flex;
gap: 10px;
}
.memory-queues-container {
display: grid;
grid-template-rows: 1fr 1fr;
gap: 20px;
height: 100%;
}
.memory-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.memory-panel {
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
padding: 15px;
}
.memory-panel h3 {
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.stack {
display: flex;
flex-direction: column-reverse;
gap: 5px;
min-height: 150px;
}
.stack-frame {
background: rgba(255, 255, 255, 0.15);
padding: 10px;
border-radius: 8px;
border-left: 4px solid #4CAF50;
}
.stack-frame.active {
background: rgba(76, 175, 80, 0.3);
box-shadow: 0 0 10px rgba(76, 175, 80, 0.5);
}
.heap {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: 10px;
min-height: 150px;
}
.heap-object {
background: rgba(255, 255, 255, 0.15);
padding: 10px;
border-radius: 8px;
border-left: 4px solid #2196F3;
text-align: center;
position: relative;
overflow: hidden;
}
.heap-object.referenced::after {
content: '';
position: absolute;
top: 0;
right: 0;
width: 10px;
height: 10px;
background: #FF9800;
border-radius: 50%;
}
.heap-object.garbage {
opacity: 0.5;
background: rgba(255, 0, 0, 0.2);
}
.queues-container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 20px;
}
.queue {
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
padding: 15px;
}
.queue h3 {
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.task {
background: rgba(255, 255, 255, 0.15);
padding: 10px;
border-radius: 8px;
margin-bottom: 8px;
display: flex;
align-items: center;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.task.micro {
border-left: 4px solid #4CAF50;
}
.task.macro {
border-left: 4px solid #2196F3;
}
.task.animation {
border-left: 4px solid #FF9800;
}
.task.executing {
background: rgba(255, 255, 255, 0.3);
transform: scale(1.02);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
}
.task.completed {
opacity: 0.7;
transform: scale(0.98);
}
.task-icon {
margin-right: 10px;
font-size: 1.2rem;
}
.task-progress {
position: absolute;
bottom: 0;
left: 0;
height: 3px;
background: rgba(255, 255, 255, 0.7);
width: 0%;
transition: width 0.1s linear;
}
.render-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.render-area {
height: 200px;
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
}
.render-box {
width: 100px;
height: 100px;
background: linear-gradient(45deg, #ff0080, #00ff80);
border-radius: 10px;
transition: all 0.5s ease;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}
.render-box.rendering {
animation: renderAnimation 0.8s ease;
}
@keyframes renderAnimation {
0% { transform: scale(0.8) rotate(0deg); opacity: 0.5; }
50% { transform: scale(1.1) rotate(180deg); opacity: 1; }
100% { transform: scale(1) rotate(360deg); opacity: 1; }
}
.event-loop {
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.loop-circle {
width: 300px;
height: 300px;
border: 3px dashed rgba(255, 255, 255, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
position: relative;
animation: rotate 20s linear infinite;
}
.loop-step {
position: absolute;
width: 80px;
height: 80px;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
text-align: center;
padding: 10px;
transition: all 0.3s ease;
}
.loop-step.active {
background: rgba(255, 255, 255, 0.3);
box-shadow: 0 0 15px rgba(255, 255, 255, 0.5);
}
.loop-step-1 {
top: -40px;
left: 50%;
transform: translateX(-50%);
}
.loop-step-2 {
bottom: -40px;
left: 50%;
transform: translateX(-50%);
}
.loop-step-3 {
left: -40px;
top: 50%;
transform: translateY(-50%);
}
.loop-step-4 {
right: -40px;
top: 50%;
transform: translateY(-50%);
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.output-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.output-panel {
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
padding: 15px;
height: 300px;
display: flex;
flex-direction: column;
}
.output-panel h3 {
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
.console-output {
background: rgba(0, 0, 0, 0.5);
border-radius: 5px;
padding: 10px;
flex: 1;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
line-height: 1.5;
}
.console-line {
margin-bottom: 5px;
}
.console-line.log {
color: #fff;
}
.console-line.error {
color: #ff6b6b;
}
.console-line.warn {
color: #ffd93d;
}
.log-container {
max-height: 300px;
overflow-y: auto;
background: rgba(0, 0, 0, 0.3);
border-radius: 10px;
padding: 15px;
}
.log-entry {
padding: 8px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
}
.log-time {
color: #FFD700;
margin-right: 10px;
font-family: 'Courier New', monospace;
}
.log-message.micro {
color: #4CAF50;
}
.log-message.macro {
color: #2196F3;
}
.log-message.animation {
color: #FF9800;
}
.log-message.render {
color: #E91E63;
}
.log-message.memory {
color: #9C27B0;
}
.stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 15px;
margin-top: 20px;
}
.stat-card {
background: rgba(255, 255, 255, 0.1);
padding: 15px;
border-radius: 10px;
text-align: center;
}
.stat-value {
font-size: 1.8rem;
font-weight: bold;
margin: 5px 0;
}
.stat-label {
font-size: 0.8rem;
opacity: 0.8;
}
@media (max-width: 1400px) {
.main-layout {
grid-template-columns: 1fr;
}
.memory-queues-container {
grid-template-rows: auto;
}
.queues-container {
grid-template-columns: 1fr;
}
.memory-container {
grid-template-columns: 1fr;
}
.render-container {
grid-template-columns: 1fr;
}
.output-container {
grid-template-columns: 1fr;
}
.stats {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.controls {
flex-direction: column;
}
.stats {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<!-- 标题 -->
<header>
<h1>浏览器事件循环、内存管理与渲染模拟</h1>
<p class="subtitle">可视化展示浏览器事件循环机制、内存管理(调用栈、堆)和渲染过程的完整执行流程。支持自定义JavaScript代码模拟。</p>
</header>
<!-- 控制面板 -->
<div class="panel">
<h2><span class="icon">⚙️</span> 控制面板</h2>
<div class="controls">
<button id="add-microtask">
<span>⚡</span> 添加微任务
</button>
<button id="add-macrotask">
<span>⏰</span> 添加宏任务
</button>
<button id="add-animation">
<span>🎬</span> 添加动画任务
</button>
<button id="start-execution">
<span>▶️</span> 开始执行
</button>
<button id="pause-execution">
<span>⏸️</span> 暂停执行
</button>
<button id="reset-all">
<span>🔄</span> 重置模拟
</button>
</div>
<div class="speed-controls">
<label for="execution-speed">执行速度:</label>
<input type="range" id="execution-speed" min="1" max="10" value="5">
<span id="speed-value">中等</span>
</div>
<div class="stats">
<div class="stat-card">
<div class="stat-label">已执行任务</div>
<div class="stat-value" id="executed-tasks">0</div>
</div>
<div class="stat-card">
<div class="stat-label">队列中任务</div>
<div class="stat-value" id="queued-tasks">3</div>
</div>
<div class="stat-card">
<div class="stat-label">渲染次数</div>
<div class="stat-value" id="render-count">0</div>
</div>
<div class="stat-card">
<div class="stat-label">堆对象数</div>
<div class="stat-value" id="heap-objects">0</div>
</div>
</div>
</div>
<!-- 主内容区域 -->
<div class="main-layout">
<!-- 左侧:代码编辑器 -->
<div class="panel">
<h2><span class="icon">💻</span> 自定义代码模拟</h2>
<div class="code-editor">
<textarea class="code-input" id="code-input" placeholder="粘贴或输入JavaScript代码...">// 示例代码
console.log('脚本开始');
setTimeout(() => {
console.log('setTimeout回调');
}, 0);
Promise.resolve().then(() => {
console.log('Promise.then回调');
});
requestAnimationFrame(() => {
console.log('requestAnimationFrame回调');
});
let user = { name: '张三', age: 30 };
let data = [1, 2, 3, 4, 5];
console.log('脚本结束');</textarea>
<div class="code-controls">
<button id="parse-code">
<span>🔍</span> 解析代码
</button>
<button id="load-example">
<span>📋</span> 加载示例
</button>
</div>
</div>
</div>
<!-- 右侧:内存管理和任务队列 -->
<div class="memory-queues-container">
<!-- 内存管理 -->
<div class="panel">
<h2><span class="icon">🧠</span> 内存管理</h2>
<div class="memory-container">
<div class="memory-panel">
<h3>调用栈 (Call Stack) <span id="stack-size">1</span></h3>
<div class="stack" id="call-stack">
<div class="stack-frame active">
<strong>全局执行上下文</strong>
<div>变量: 全局变量</div>
</div>
</div>
</div>
<div class="memory-panel">
<h3>堆 (Heap) <span id="heap-size">0</span></h3>
<div class="heap" id="heap-memory"></div>
</div>
</div>
</div>
<!-- 任务队列 -->
<div class="panel">
<h2><span class="icon">📊</span> 任务队列</h2>
<div class="queues-container">
<div class="queue">
<h3>宏任务队列 <span id="macro-count">1</span></h3>
<div id="macro-queue">
<div class="task macro">
<span class="task-icon">⏰</span>
<span>初始脚本执行</span>
<div class="task-progress"></div>
</div>
</div>
</div>
<div class="queue">
<h3>微任务队列 <span id="micro-count">0</span></h3>
<div id="micro-queue"></div>
</div>
<div class="queue">
<h3>动画帧队列 <span id="animation-count">0</span></h3>
<div id="animation-queue"></div>
</div>
</div>
</div>
</div>
</div>
<!-- 渲染模拟 -->
<div class="render-container">
<div class="panel">
<h2><span class="icon">🖥️</span> 渲染模拟</h2>
<div class="render-area">
<div class="render-box" id="render-box">
渲染区域
</div>
</div>
</div>
<div class="panel">
<h2><span class="icon">🔄</span> 事件循环</h2>
<div class="event-loop">
<div class="loop-circle">
<div class="loop-step loop-step-1" id="loop-step-1">
<strong>执行宏任务</strong>
</div>
<div class="loop-step loop-step-2" id="loop-step-2">
<strong>执行微任务</strong>
</div>
<div class="loop-step loop-step-3" id="loop-step-3">
<strong>执行动画回调</strong>
</div>
<div class="loop-step loop-step-4" id="loop-step-4">
<strong>渲染页面</strong>
</div>
</div>
</div>
</div>
</div>
<!-- 输出区域 -->
<div class="output-container">
<div class="panel">
<h2><span class="icon">📝</span> 执行日志</h2>
<div class="log-container" id="log-container">
<div class="log-entry">
<span class="log-time">00:00:00</span>
<span class="log-message">模拟器已初始化,准备执行任务</span>
</div>
</div>
</div>
<div class="panel">
<h2><span class="icon">📄</span> 控制台输出</h2>
<div class="output-panel">
<div class="console-output" id="console-output">
<div class="console-line log">控制台输出区域...</div>
</div>
</div>
</div>
</div>
</div>
<script>
// 任务队列
const macroQueue = document.getElementById('macro-queue');
const microQueue = document.getElementById('micro-queue');
const animationQueue = document.getElementById('animation-queue');
const renderBox = document.getElementById('render-box');
// 内存管理
const callStack = document.getElementById('call-stack');
const heapMemory = document.getElementById('heap-memory');
// 计数显示
const macroCount = document.getElementById('macro-count');
const microCount = document.getElementById('micro-count');
const animationCount = document.getElementById('animation-count');
const stackSize = document.getElementById('stack-size');
const heapSize = document.getElementById('heap-size');
const executedTasks = document.getElementById('executed-tasks');
const queuedTasks = document.getElementById('queued-tasks');
const renderCount = document.getElementById('render-count');
const heapObjects = document.getElementById('heap-objects');
// 事件循环步骤
const loopStep1 = document.getElementById('loop-step-1');
const loopStep2 = document.getElementById('loop-step-2');
const loopStep3 = document.getElementById('loop-step-3');
const loopStep4 = document.getElementById('loop-step-4');
// 日志容器
const logContainer = document.getElementById('log-container');
// 控制台输出
const consoleOutput = document.getElementById('console-output');
// 代码输入
const codeInput = document.getElementById('code-input');
// 控制变量
let taskId = 1;
let executedCount = 0;
let renderCountValue = 0;
let executionSpeed = 5;
let isExecuting = false;
let currentTask = null;
let progressInterval = null;
let heapId = 1;
let stackFrames = [];
let heapObjectsList = [];
// 速度映射
const speedMap = {
1: { name: "非常慢", delay: 2000 },
2: { name: "很慢", delay: 1500 },
3: { name: "慢", delay: 1000 },
4: { name: "较慢", delay: 800 },
5: { name: "中等", delay: 600 },
6: { name: "较快", delay: 400 },
7: { name: "快", delay: 300 },
8: { name: "很快", delay: 200 },
9: { name: "非常快", delay: 100 },
10: { name: "极速", delay: 50 }
};
// 添加任务函数
document.getElementById('add-microtask').addEventListener('click', () => {
addTask('micro', `微任务 ${taskId++}`, 'Promise.then()');
});
document.getElementById('add-macrotask').addEventListener('click', () => {
addTask('macro', `宏任务 ${taskId++}`, 'setTimeout()');
});
document.getElementById('add-animation').addEventListener('click', () => {
addTask('animation', `动画任务 ${taskId++}`, 'requestAnimationFrame()');
});
document.getElementById('start-execution').addEventListener('click', () => {
if (!isExecuting) {
isExecuting = true;
executeEventLoop();
}
});
document.getElementById('pause-execution').addEventListener('click', () => {
isExecuting = false;
if (currentTask) {
currentTask.classList.remove('executing');
currentTask = null;
}
if (progressInterval) {
clearInterval(progressInterval);
progressInterval = null;
}
resetLoopSteps();
addLog('执行已暂停', 'system');
});
document.getElementById('reset-all').addEventListener('click', () => {
resetSimulation();
});
// 代码解析
document.getElementById('parse-code').addEventListener('click', () => {
parseAndSimulateCode(codeInput.value);
});
// 加载示例代码
document.getElementById('load-example').addEventListener('click', () => {
codeInput.value = `// 示例代码
console.log('脚本开始');
setTimeout(() => {
console.log('setTimeout回调');
}, 0);
Promise.resolve().then(() => {
console.log('Promise.then回调');
});
requestAnimationFrame(() => {
console.log('requestAnimationFrame回调');
});
let user = { name: '张三', age: 30 };
let data = [1, 2, 3, 4, 5];
console.log('脚本结束');`;
});
// 速度控制
document.getElementById('execution-speed').addEventListener('input', (e) => {
executionSpeed = parseInt(e.target.value);
document.getElementById('speed-value').textContent = speedMap[executionSpeed].name;
});
// 解析并模拟代码
function parseAndSimulateCode(code) {
resetSimulation();
// 清空队列,但保留初始宏任务
macroQueue.innerHTML = '<div class="task macro"><span class="task-icon">⏰</span><span>初始脚本执行</span><div class="task-progress"></div></div>';
microQueue.innerHTML = '';
animationQueue.innerHTML = '';
// 重置任务ID
taskId = 1;
// 添加日志
addLog('开始解析自定义代码', 'system');
// 清空控制台输出
consoleOutput.innerHTML = '<div class="console-line log">解析代码中...</div>';
// 简单的代码解析逻辑
const lines = code.split('\n');
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
// 跳过空行和注释
if (!line || line.startsWith('//')) continue;
// 检测setTimeout
if (line.includes('setTimeout')) {
addTask('macro', `宏任务 ${taskId++}`, 'setTimeout回调');
addLog(`检测到setTimeout: ${line}`, 'macro');
addConsoleOutput('log', '检测到setTimeout,已添加到宏任务队列');
}
// 检测setInterval
if (line.includes('setInterval')) {
addTask('macro', `宏任务 ${taskId++}`, 'setInterval回调');
addLog(`检测到setInterval: ${line}`, 'macro');
addConsoleOutput('log', '检测到setInterval,已添加到宏任务队列');
}
// 检测Promise.then
if (line.includes('.then(') || line.includes('.catch(') || line.includes('.finally(')) {
addTask('micro', `微任务 ${taskId++}`, 'Promise回调');
addLog(`检测到Promise回调: ${line}`, 'micro');
addConsoleOutput('log', '检测到Promise回调,已添加到微任务队列');
}
// 检测requestAnimationFrame
if (line.includes('requestAnimationFrame')) {
addTask('animation', `动画任务 ${taskId++}`, 'requestAnimationFrame回调');
addLog(`检测到requestAnimationFrame: ${line}`, 'animation');
addConsoleOutput('log', '检测到requestAnimationFrame,已添加到动画任务队列');
}
// 检测变量声明
if (line.includes('let ') || line.includes('const ') || line.includes('var ')) {
const varName = extractVariableName(line);
if (varName) {
// 检测对象或数组
if (line.includes('{') || line.includes('[')) {
const objectType = line.includes('{') ? 'Object' : 'Array';
addHeapObject(objectType, varName, true);
addLog(`检测到${objectType}声明: ${varName}`, 'memory');
addConsoleOutput('log', `检测到${objectType}声明: ${varName}`);
}
}
}
// 检测函数调用
if (line.includes('(') && line.includes(')') &&
!line.includes('function') &&
!line.includes('=>') &&
!line.includes('setTimeout') &&
!line.includes('setInterval') &&
!line.includes('requestAnimationFrame')) {
const funcName = extractFunctionName(line);
if (funcName && funcName !== 'console.log') {
addLog(`检测到函数调用: ${funcName}`, 'system');
addConsoleOutput('log', `检测到函数调用: ${funcName}`);
}
}
// 检测console.log
if (line.includes('console.log')) {
const message = extractConsoleMessage(line);
if (message) {
addConsoleOutput('log', message);
}
}
}
addLog('代码解析完成', 'system');
addConsoleOutput('log', '代码解析完成,准备执行');
updateQueueCounts();
}
// 提取变量名
function extractVariableName(line) {
const match = line.match(/(let|const|var)\s+(\w+)/);
return match ? match[2] : null;
}
// 提取函数名
function extractFunctionName(line) {
const match = line.match(/(\w+)\(/);
return match ? match[1] : null;
}
// 提取console.log消息
function extractConsoleMessage(line) {
const match = line.match(/console\.log\((['"`])(.*?)\1\)/);
return match ? match[2] : null;
}
// 添加任务到队列
function addTask(type, name, description) {
const taskElement = document.createElement('div');
taskElement.className = `task ${type}`;
taskElement.innerHTML = `
<span class="task-icon">${getTaskIcon(type)}</span>
<span>${description || name}</span>
<div class="task-progress"></div>
`;
switch(type) {
case 'macro':
macroQueue.appendChild(taskElement);
break;
case 'micro':
microQueue.appendChild(taskElement);
break;
case 'animation':
animationQueue.appendChild(taskElement);
break;
}
updateQueueCounts();
addLog(`已添加${getTaskTypeName(type)}: ${description || name}`, type);
}
// 获取任务图标
function getTaskIcon(type) {
switch(type) {
case 'macro': return '⏰';
case 'micro': return '⚡';
case 'animation': return '🎬';
default: return '📝';
}
}
// 获取任务类型名称
function getTaskTypeName(type) {
switch(type) {
case 'macro': return '宏任务';
case 'micro': return '微任务';
case 'animation': return '动画任务';
default: return '任务';
}
}
// 更新队列计数
function updateQueueCounts() {
macroCount.textContent = macroQueue.children.length;
microCount.textContent = microQueue.children.length;
animationCount.textContent = animationQueue.children.length;
const totalQueued = macroQueue.children.length + microQueue.children.length + animationQueue.children.length;
queuedTasks.textContent = totalQueued;
}
// 添加控制台输出
function addConsoleOutput(type, message) {
const now = new Date();
const timeString = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
const outputLine = document.createElement('div');
outputLine.className = `console-line ${type}`;
outputLine.innerHTML = `<span class="console-time">${timeString}</span> ${message}`;
consoleOutput.appendChild(outputLine);
consoleOutput.scrollTop = consoleOutput.scrollHeight;
}
// 添加栈帧
function pushStackFrame(name, variables) {
const frameId = `frame-${Date.now()}`;
const frame = {
id: frameId,
name: name,
variables: variables || []
};
stackFrames.push(frame);
const frameElement = document.createElement('div');
frameElement.className = 'stack-frame';
frameElement.id = frameId;
frameElement.innerHTML = `
<strong>${name}</strong>
<div>变量: ${variables ? variables.join(', ') : '无'}</div>
`;
// 取消之前活跃的栈帧
const activeFrames = callStack.querySelectorAll('.stack-frame.active');
activeFrames.forEach(frame => frame.classList.remove('active'));
// 添加新栈帧并标记为活跃
callStack.appendChild(frameElement);
frameElement.classList.add('active');
stackSize.textContent = stackFrames.length;
return frameId;
}
// 移除栈帧
function popStackFrame(frameId) {
const frameIndex = stackFrames.findIndex(frame => frame.id === frameId);
if (frameIndex !== -1) {
stackFrames.splice(frameIndex, 1);
}
const frameElement = document.getElementById(frameId);
if (frameElement) {
frameElement.remove();
}
// 激活上一个栈帧
if (stackFrames.length > 0) {
const lastFrameId = stackFrames[stackFrames.length - 1].id;
const lastFrameElement = document.getElementById(lastFrameId);
if (lastFrameElement) {
lastFrameElement.classList.add('active');
}
}
stackSize.textContent = stackFrames.length;
}
// 添加堆对象
function addHeapObject(type, value, referenced = true) {
const objectId = `heap-${heapId++}`;
const object = {
id: objectId,
type: type,
value: value,
referenced: referenced
};
heapObjectsList.push(object);
const objectElement = document.createElement('div');
objectElement.className = `heap-object ${referenced ? 'referenced' : 'garbage'}`;
objectElement.id = objectId;
objectElement.innerHTML = `
<div><strong>${type}</strong></div>
<div>${value}</div>
`;
heapMemory.appendChild(objectElement);
heapSize.textContent = heapObjectsList.length;
heapObjects.textContent = heapObjectsList.length;
return objectId;
}
// 标记堆对象为垃圾
function markHeapObjectAsGarbage(objectId) {
const object = heapObjectsList.find(obj => obj.id === objectId);
if (object) {
object.referenced = false;
const objectElement = document.getElementById(objectId);
if (objectElement) {
objectElement.classList.remove('referenced');
objectElement.classList.add('garbage');
}
}
}
// 执行垃圾回收
function runGarbageCollection() {
const garbageObjects = heapObjectsList.filter(obj => !obj.referenced);
garbageObjects.forEach(obj => {
const objectElement = document.getElementById(obj.id);
if (objectElement) {
objectElement.remove();
}
const objectIndex = heapObjectsList.findIndex(o => o.id === obj.id);
if (objectIndex !== -1) {
heapObjectsList.splice(objectIndex, 1);
}
});
heapSize.textContent = heapObjectsList.length;
heapObjects.textContent = heapObjectsList.length;
if (garbageObjects.length > 0) {
addLog(`垃圾回收: 释放了 ${garbageObjects.length} 个对象`, 'memory');
addConsoleOutput('log', `垃圾回收: 释放了 ${garbageObjects.length} 个对象`);
}
}
// 模拟事件循环
async function executeEventLoop() {
if (!isExecuting) return;
// 执行宏任务
if (macroQueue.children.length > 0) {
await executeTask('macro', macroQueue);
if (!isExecuting) return;
}
// 执行微任务
while (microQueue.children.length > 0 && isExecuting) {
await executeTask('micro', microQueue);
if (!isExecuting) return;
}
// 执行动画任务
if (animationQueue.children.length > 0 && isExecuting) {
await executeTask('animation', animationQueue);
if (!isExecuting) return;
}
// 渲染
if (isExecuting) {
await executeRender();
}
// 垃圾回收
if (Math.random() > 0.7) { // 随机触发垃圾回收
runGarbageCollection();
}
// 如果还有任务,继续执行
if ((macroQueue.children.length > 0 || microQueue.children.length > 0 || animationQueue.children.length > 0) && isExecuting) {
setTimeout(executeEventLoop, 100);
} else {
isExecuting = false;
addLog('所有任务执行完成', 'system');
addConsoleOutput('log', '所有任务执行完成');
}
}
// 执行单个任务
async function executeTask(type, queue) {
const task = queue.children[0];
if (!task) return;
currentTask = task;
task.classList.add('executing');
// 高亮对应的事件循环步骤
highlightLoopStep(type);
// 添加执行日志
const taskDescription = task.textContent.trim();
addLog(`开始执行: ${taskDescription}`, type);
addConsoleOutput('log', `开始执行: ${taskDescription}`);
// 模拟函数调用 - 添加栈帧
const frameId = pushStackFrame(
taskDescription,
['局部变量1', '局部变量2']
);
// 模拟堆分配 - 随机创建一些对象
if (Math.random() > 0.5) {
const objectTypes = ['Object', 'Array', 'Function', 'String'];
const objectType = objectTypes[Math.floor(Math.random() * objectTypes.length)];
const objectValue = generateRandomValue(objectType);
addHeapObject(objectType, objectValue, true);
addLog(`堆分配: 创建了 ${objectType} 对象`, 'memory');
addConsoleOutput('log', `堆分配: 创建了 ${objectType} 对象`);
}
// 开始进度条动画
const progressBar = task.querySelector('.task-progress');
let progress = 0;
progressInterval = setInterval(() => {
progress += 2;
progressBar.style.width = `${progress}%`;
if (progress >= 100) {
clearInterval(progressInterval);
progressInterval = null;
}
}, speedMap[executionSpeed].delay / 50);
// 等待任务执行完成
await delay(speedMap[executionSpeed].delay);
if (progressInterval) {
clearInterval(progressInterval);
progressInterval = null;
}
// 模拟函数返回 - 移除栈帧
popStackFrame(frameId);
// 随机标记一些对象为垃圾
if (heapObjectsList.length > 0 && Math.random() > 0.7) {
const randomIndex = Math.floor(Math.random() * heapObjectsList.length);
const randomObject = heapObjectsList[randomIndex];
if (randomObject && randomObject.referenced) {
markHeapObjectAsGarbage(randomObject.id);
addLog(`对象 ${randomObject.type} 不再被引用`, 'memory');
addConsoleOutput('log', `对象 ${randomObject.type} 不再被引用`);
}
}
task.classList.remove('executing');
task.classList.add('completed');
// 短暂显示完成状态后移除
await delay(300);
task.remove();
// 更新计数
executedCount++;
executedTasks.textContent = executedCount;
updateQueueCounts();
currentTask = null;
addLog(`完成执行: ${taskDescription}`, type);
addConsoleOutput('log', `完成执行: ${taskDescription}`);
}
// 执行渲染
async function executeRender() {
highlightLoopStep('render');
addLog('开始页面渲染', 'render');
addConsoleOutput('log', '开始页面渲染');
renderBox.classList.add('rendering');
renderCountValue++;
renderCount.textContent = renderCountValue;
await delay(speedMap[executionSpeed].delay);
renderBox.classList.remove('rendering');
addLog('页面渲染完成', 'render');
addConsoleOutput('log', '页面渲染完成');
}
// 高亮事件循环步骤
function highlightLoopStep(type) {
resetLoopSteps();
switch(type) {
case 'macro':
loopStep1.classList.add('active');
break;
case 'micro':
loopStep2.classList.add('active');
break;
case 'animation':
loopStep3.classList.add('active');
break;
case 'render':
loopStep4.classList.add('active');
break;
}
}
// 重置事件循环步骤高亮
function resetLoopSteps() {
loopStep1.classList.remove('active');
loopStep2.classList.remove('active');
loopStep3.classList.remove('active');
loopStep4.classList.remove('active');
}
// 生成随机值
function generateRandomValue(type) {
switch(type) {
case 'Object':
return `{key: "value${Math.floor(Math.random() * 100)}"}`;
case 'Array':
return `[${Math.floor(Math.random() * 10)}, ${Math.floor(Math.random() * 10)}]`;
case 'Function':
return `function${Math.floor(Math.random() * 10)}()`;
case 'String':
return `"string${Math.floor(Math.random() * 100)}"`;
default:
return "unknown";
}
}
// 重置模拟
function resetSimulation() {
isExecuting = false;
if (currentTask) {
currentTask.classList.remove('executing');
currentTask = null;
}
if (progressInterval) {
clearInterval(progressInterval);
progressInterval = null;
}
// 清空队列
macroQueue.innerHTML = '<div class="task macro"><span class="task-icon">⏰</span><span>初始脚本执行</span><div class="task-progress"></div></div>';
microQueue.innerHTML = '';
animationQueue.innerHTML = '';
// 清空内存
callStack.innerHTML = '<div class="stack-frame active"><strong>全局执行上下文</strong><div>变量: 全局变量</div></div>';
heapMemory.innerHTML = '';
// 重置内存数据
stackFrames = [];
heapObjectsList = [];
heapId = 1;
// 重置计数
taskId = 1;
executedCount = 0;
renderCountValue = 0;
executedTasks.textContent = '0';
renderCount.textContent = '0';
stackSize.textContent = '1';
heapSize.textContent = '0';
heapObjects.textContent = '0';
// 更新队列计数
updateQueueCounts();
// 重置事件循环步骤
resetLoopSteps();
// 清空日志
logContainer.innerHTML = '';
addLog('模拟器已重置', 'system');
// 清空控制台输出
consoleOutput.innerHTML = '<div class="console-line log">模拟器已重置</div>';
}
// 添加日志
function addLog(message, type) {
const now = new Date();
const timeString = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
const logEntry = document.createElement('div');
logEntry.className = 'log-entry';
logEntry.innerHTML = `
<span class="log-time">${timeString}</span>
<span class="log-message ${type}">${message}</span>
`;
logContainer.appendChild(logEntry);
logContainer.scrollTop = logContainer.scrollHeight;
}
// 延迟函数
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 初始化示例任务
function initializeExampleTasks() {
addTask('micro', '微任务 1', 'Promise.then()');
addTask('macro', '宏任务 1', 'setTimeout()');
addTask('animation', '动画任务 1', 'requestAnimationFrame()');
addTask('micro', '微任务 2', 'Promise.resolve().then()');
addTask('macro', '宏任务 2', 'setInterval()');
// 初始化一些堆对象
addHeapObject('Object', '{name: "user", age: 30}');
addHeapObject('Array', '[1, 2, 3, 4, 5]');
addHeapObject('Function', 'function clickHandler()');
}
// 页面加载后初始化
window.addEventListener('load', () => {
initializeExampleTasks();
addLog('模拟器已初始化,准备执行任务', 'system');
addConsoleOutput('log', '模拟器已初始化,准备执行任务');
});
</script>
</body>
</html>