初识 Console 对象与基础方法
| 方法名称 | 语法 | 用途说明 | 代码示例 | 注意事项 |
|---|
| console.log | console.log(value1, value2, ...) | 输出普通信息,用于常规调试和变量查看 | console.log("用户名:", "Alice", "年龄:", 25); | 输出内容格式清晰,支持多个参数拼接 |
| console.info | console.info(message) | 输出提示性信息,通常带信息图标 | console.info("系统已启动"); | 视觉上弱于 log,适合非关键提示 |
| console.warn | console.warn(message) | 输出警告信息,以黄色高亮显示 | console.warn("此 API 即将废弃"); | 提醒开发者注意潜在问题,不影响程序运行 |
| console.error | console.error(message) | 输出错误信息,通常为红色,表示严重问题 | console.error("网络请求失败:", status); | 常用于异常捕获,会触发错误堆栈记录 |
| console.debug | console.debug(message) | 输出调试信息,级别低于 log | console.debug("进入函数处理流程"); | 需在浏览器设置中启用 verbose 级别日志才能看到 |
| console.clear | console.clear() | 清空控制台输出内容 | console.clear(); | 执行后控制台历史记录被清除,不可恢复 |
| console.table | console.table(data[, columns]) | 以表格形式展示数组或对象数据 | console.table([{name:"Alice", age:25}]); | 数据结构规整时可读性强,适合查看列表型数据 |
Console 高级输出方法与格式化
| 方法名称 | 语法 | 用途说明 | 代码示例 | 注意事项 |
|---|
| console.group | console.group(label) | 创建一个折叠的输出分组,便于组织日志 | console.group("用户操作");
console.log("登录");
console.groupEnd(); | 后续的输出会缩进显示在该组内,需配合 groupEnd 使用 |
| console.groupCollapsed | console.groupCollapsed(label) | 创建一个默认折叠的分组,节省控制台空间 | console.groupCollapsed("详细配置");
console.log(config);
console.groupEnd(); | 用户需手动展开查看内容,适合非关键细节信息 |
| console.groupEnd | console.groupEnd() | 结束当前最内层的分组 | console.group("步骤1");
console.log("执行中...");
console.groupEnd(); | 必须与 group 或 groupCollapsed 成对使用 |
| console.time | console.time(label) | 启动一个计时器,用于测量代码执行时间 | console.time("加载耗时");
slowFunction();
console.timeEnd("加载耗时"); | label 用于标识计时器,必须与 timeEnd 的 label 一致 |
| console.timeEnd | console.timeEnd(label) | 停止指定的计时器并输出耗时(毫秒) | console.time("fetch");
fetch(url).then(() => console.timeEnd("fetch")); | 输出格式为 “label: X ms”,自动清除计时器 |
| console.assert | console.assert(condition, message) | 当条件为 false 时输出错误信息,否则无输出 | console.assert(x > 0, "x 必须为正数"); | 不会中断程序执行,仅用于断言调试 |
| console.count | console.count(label) | 统计某条日志被调用的次数 | for(let i=0; i<3; i++) console.count("循环"); | 每次调用自动递增计数,label 相同则共享计数器 |
| console.countReset | console.countReset(label) | 重置指定 label 的计数器 | console.count("请求");
console.countReset("请求"); | 重置后计数从 1 重新开始 |
| console.trace | console.trace() | 输出当前执行位置的函数调用栈 | function a() { b(); }
function b() { console.trace(); } | 有助于追踪函数调用路径,定位深层调用问题 |
理解 Console 方法的执行上下文
| 方法名称 | 语法 | 用途说明 | 代码示例 | 注意事项 |
|---|
| console.log | console.log(...) | 输出信息,this 不依赖调用环境 | const obj = { name: "test" };
const log = console.log;
log(obj.name); | console 方法独立调用时 this 指向安全,无上下文问题 |
| Function.prototype.call | func.call(thisArg, arg1, ...) | 强制指定函数调用时的 this 值 | console.log.call(console, "强制上下文"); | 若未绑定 console,直接调用可能丢失上下文导致报错 |
| Function.prototype.apply | func.apply(thisArg, [args]) | 同 call,参数以数组形式传入 | console.info.apply(console, ["应用调用", 123]); | 适用于动态参数场景,常用于封装日志转发 |
| Function.prototype.bind | func.bind(thisArg, arg1, ...) | 创建新函数,永久绑定 this 及部分参数 | const boundLog = console.log.bind(console);
boundLog("绑定后调用"); | 推荐用于保存带上下文的 console 方法,防止被篡改覆盖 |
| 间接调用 | (0, console.log)(value) | 利用逗号操作符间接调用方法,模拟上下文丢失 | const log = (0, console.log);
log("间接调用"); | 此方式会丢失原始 this,可能导致某些环境调用失败 |
| window.console | window.console.method(...) | 显式通过 window 访问 console 对象 | window.console.warn("通过 window 调用"); | 确保访问的是全局 console,避免被局部变量遮蔽 |
| 保存原始引用 | const originalLog = console.log; | 在 hook 前保存原始方法引用 | const originalWarn = console.warn;
console.warn = function() { originalWarn.apply(console, arguments);
}; | 必须在修改前保存,否则无法恢复原始行为 |
| 严格模式影响 | "use strict"; console.log(...); | 严格模式下不影响 console 方法的 this 指向 | "use strict";
console.error("严格模式下正常输出"); | console 方法为宿主对象方法,其行为不受 strict 影响 |
Console 方法的重写(Overriding)基础
| 方法名称 | 语法 | 用途说明 | 代码示例 | 注意事项 |
|---|
| console.log 重写 | console.log = function(...args) { ... } | 拦截所有 console.log 调用,插入自定义逻辑 | const original = console.log;
console.log = function(...args) { original.apply(console, args); // 可添加日志上报、格式化等
}; | 必须先保存原始方法,否则无法恢复或转发原始输出 |
| console.warn 重写 | console.warn = function(message) { ... } | 拦截警告信息,可用于过滤或增强输出 | const oldWarn = console.warn;
console.warn = function(msg) { oldWarn.call(console, "[WARN] " + msg);
}; | 注意调用 call 或 apply 并传入 console 作为 this |
| console.error 重写 | console.error = function() { ... } | 捕获错误日志,便于错误监控与上报 | const realError = console.error;
console.error = function() { analytics.track("console_error", arguments); realError.apply(console, arguments);
}; | 可结合错误堆栈收集,用于前端异常监控系统 |
| 保存原始引用 | const originalMethod = console.method; | 在重写前缓存原始方法,确保可调用原始功能 | const originalLog = console.log;
console.log = function() { console.log = originalLog; // 示例:仅第一次重写
}; | 保存必须在重写之前完成,否则引用丢失 |
| 方法转发 | originalMethod.apply(console, arguments) | 在重写函数中调用原始方法,保持原有行为 | console.log = function(...args) { console.log = () => {}; // 阻止二次输出 originalLog(...args);
}; | 推荐使用 apply(arguments) 或展开语法保持参数完整性 |
| 匿名函数封装 | console.log = (function() { ... })(); | 使用 IIFE 封装更复杂的 hook 逻辑 | console.log = (function(orig) { return function(...args) { orig.call(console, "[HOOKED]", ...args); };
})(console.log); | 适用于需要私有状态或配置的 hook 场景 |
| 临时禁用 | console.log = function() {}; | 简单屏蔽控制台输出,用于生产环境隐藏日志 | if (isProduction) { console.log = console.warn = console.error = () => {};
} | 完全静默,不保留原始行为,慎用于调试环境 |
| 恢复原始方法 | console.log = originalLog; | 将重写后的方法恢复为原始实现 | let original = console.log;
console.log = function() { /* 拦截 */ };
// 恢复:
console.log = original; | 确保 original 引用未被覆盖,可用于调试结束后的还原 |
构建通用 Console Hook 框架
| 方法名称 | 语法 | 用途说明 | 代码示例 | 注意事项 |
|---|
| hookConsole | hookConsole(methods, callback) | 通用 hook 函数,批量拦截指定的 console 方法 | 见下方代码块 | 必须传入有效的 console 方法名数组,避免非法属性访问 |
| unhookConsole | unhookConsole(methods, originals) | 恢复原始方法,解除 hook | 见下方代码块 | 需提前保存 originals 映射表,否则无法恢复 |
| createConsoleHook | createConsoleHook() | 返回包含 hook / unhook 的管理对象 | 见下方代码块 | 封装状态管理,避免全局变量污染,支持多次 hook / unhook |
| isHooked | isHooked(method) | 检查指定 console 方法是否已被重写 | function isHooked(method) { return console[method].toString().includes("Intercept");
} | 仅适用于添加了特定标记的 hook,通用性有限 |
| batchHook | batchHook(config) | 批量配置不同方法的不同处理逻辑 | batchHook({ log: (a) => sendToServer("LOG", a), error: (e) => captureError(e)
}); | config 应为方法名到处理函数的映射 |
| preserveOrder | preserveOrder = true | 确保原始输出顺序不被异步操作打乱 | 见下方代码块 | 若在 hook 中执行异步操作(如上报),可能影响输出顺序 |
| enableHook | enableHook(flag) | 控制 hook 是否生效,支持动态开关 | let enabled = true;
console.log = function(...args) { if (enabled) customAction(args); original.apply(console, args);
} | 用于临时关闭监控而无需解除 hook |
| getOriginalMethod | getOriginalMethod(method) | 从保存的映射中获取原始方法引用 | const originals = {};
function getOriginalMethod(m) { return originals[m];
} | 需事先在 originals 对象中保存各方法的原始引用 |
hookConsole 示例
function hookConsole(methods, cb) {
methods.forEach(method => {
const orig = console[method];
console[method] = function(...args) {
cb(method, args);
orig.apply(console, args);
};
});
}
hookConsole(['log', 'warn'], (m, a) => {
console.debug(`[Intercept] ${m}:`, ...a);
});
unhookConsole 示例
function unhookConsole(methods, originals) {
methods.forEach(method => {
if (originals[method]) {
console[method] = originals[method];
}
});
}
createConsoleHook 示例
function createConsoleHook() {
const originals = {};
function hook(methods, handler) {
methods.forEach(m => {
if (!originals[m]) originals[m] = console[m];
console[m] = (...args) => {
handler(m, args);
originals[m].apply(console, args);
};
});
}
function unhook() {
Object.keys(originals).forEach(m => {
console[m] = originals[m];
});
}
return { hook, unhook };
}
batchHook 示例
function batchHook(config) {
Object.keys(config).forEach(method => {
const orig = console[method];
console[method] = function(...args) {
config[method](args);
orig.apply(console, args);
};
});
}
batchHook({
log: (a) => sendToServer("LOG", a),
error: (e) => captureError(e)
});
高级 Hook 技术:参数拦截与修改
| 方法名称 | 语法 | 用途说明 | 代码示例 | 注意事项 |
|---|
| 参数拦截与记录 | console.log = function(...args) { log(args); orig(...args); } | 在不修改原始行为的前提下记录调用参数 | const original = console.log;
console.log = function(...args) { console.debug("[Captured]", ...args); original.apply(console, args);
}; | 确保原始方法仍被调用,避免调试时遗漏真实输出 |
| 参数修改(脱敏) | 修改 args 中的敏感数据后再传递 | 对输出内容进行过滤,如隐藏手机号、密码等 | console.log = function(...args) { const filtered = args.map(arg => typeof arg === 'string' && /password/i.test(arg) ? '[REDACTED]' : arg ); originalLog.apply(console, filtered);
}; | 修改应尽量不改变原对象,避免影响业务逻辑 |
| 深度对象脱敏 | 递归遍历对象,替换敏感字段值 | 防止用户隐私数据通过 console 泄露 | function sanitize(obj) { … } | 需注意循环引用可能导致栈溢出 |
| 条件性拦截 | 根据参数内容决定是否拦截或放行 | 实现日志过滤,仅记录特定类型或关键词的日志 | console.warn = function(...args) { if (args.some(a => /deprecated/i.test(String(a)))) { logToMonitor(...args); } originalWarn.apply(console, args);
}; | 过滤逻辑应高效,避免正则表达式过度回溯 |
| 函数参数重写 | 动态修改传入的函数参数(如回调) | 用于注入额外逻辑,如自动埋点 | 见下方代码块 | 仅适用于参数结构明确的场景,通用性较低 |
| 时间戳注入 | 在原始参数前/后自动添加时间信息 | 增强日志可读性,便于追踪执行时间 | console.log = function(...args) { const now = new Date().toISOString(); const enhanced = [[${now}]].concat(args); originalLog.apply(console, enhanced);
}; | 保持原始参数不变,仅做增强,避免影响业务逻辑 |
| 类型检查与格式化 | 根据参数类型执行不同处理 | 统一日志格式,提升调试效率 | console.log = function(...args) { const formatted = args.map(arg => { if (arg instanceof Error) return arg.stack; if (typeof arg === 'object') return JSON.stringify(arg); return arg; }); originalLog.apply(console, formatted);
}; | 注意循环引用对象可能导致 JSON.stringify 报错 |
| 异步参数捕获 | 在异步上下文中捕获 console 调用参数 | 用于追踪 Promise、setTimeout 中的日志 | 无额外语法,依赖 hook 本身已覆盖所有调用场景 | 所有 console 方法均会被统一拦截,天然支持异步上下文 |
函数参数重写示例
console.log = function(first, callback) {
if (typeof callback === 'function') {
const wrapped = (...args) => {
console.debug("Callback 开始");
const result = callback(...args);
console.debug("Callback 结束");
return result;
};
originalLog(first, wrapped);
return;
}
originalLog.apply(console, arguments);
};
防御性 Hook 与反检测机制
| 方法名称 | 语法 | 用途说明 | 代码示例 | 注意事项 |
|---|
| Object.defineProperty 永久锁定 | Object.defineProperty(console, 'log', { writable: false }) | 防止 console 方法被后续脚本覆盖 | Object.defineProperty(console, 'log', { value: console.log, writable: false, configurable: false
}); | 必须在其他脚本执行前完成锁定,否则可能已被篡改 |
| 检测是否已被 hook | console.log.toString() !== '[native code]' | 判断方法是否被重写,原生方法显示为 [native code] | function isHooked(method) { return console[method].toString().indexOf('[native code]') === -1;
} | 简单有效,但可被高级 hook 伪造 toString 表现 |
| 重写 toString 方法 | method.toString = () => '[native code]' | 伪装成原生方法,防止被检测出已被 hook | const hookedLog = console.log;
hookedLog.toString = function() { return '[native code]';
};
console.log = hookedLog; | 提高隐蔽性,对抗基于 toString 的检测机制 |
| 定时自检与恢复 | setInterval(checkAndRestore, interval) | 周期性检查 console 方法是否被覆盖并尝试恢复 | const original = { log: console.log };
setInterval(() => { if (console.log !== original.log) { console.log = original.log; }
}, 1000); | 频繁检查可能影响性能,适用于高安全场景 |
| 使用 WeakMap 存储原始引用 | const originals = new WeakMap() | 安全保存原始方法引用,防止被外部访问或篡改 | const originals = new WeakMap();
originals.set(console, { log: console.log });
// 使用时:
const orig = originals.get(console).log; | 比普通对象更安全,引用清理由 GC 自动管理 |
| 防止 delete 操作 | configurable: false | 禁止通过 delete console.log 删除方法 | Object.defineProperty(console, 'log', { configurable: false
}); | 需结合 writable: false 使用,增强防御能力 |
| 多层包装保护 | 包装函数嵌套多层并混淆 | 增加逆向分析难度,防止被轻易绕过 | 见下方代码块 | 可结合代码混淆工具使用,提升安全性 |
| 检测 arguments.callee | 禁用或监控递归调用 | 防范通过递归或栈追踪发现 hook 逻辑 | 不推荐主动使用 arguments.callee(已弃用) | 现代浏览器已弃用,更多用于检测异常调用模式 |
| 使用 Proxy 拦截访问 | new Proxy(console, { get: () => handler }) | 全面控制 console 属性访问,实现动态拦截 | const handler = { apply(target, thisArg, args) { logAccess(); return target.apply(thisArg, args);
} };
console.log = new Proxy(console.log, handler); | 兼容性要求较高(ES6+),部分旧浏览器不支持 |
多层包装保护示例
console.log = (function(orig) {
return (function(a) {
return function(...args) {
monitor(args);
a.apply(console, args);
}
})(orig)
})(console.log);
实际应用场景与案例分析
| 方法名称 | 语法 | 用途说明 | 代码示例 | 注意事项 |
|---|
| 前端监控埋点 | hook console 并上报日志类型与频率 | 收集用户操作中的关键日志,用于行为分析 | const original = console.error;
console.error = function(...args) { analytics.track('js_error', { message: args[0], stack: args.find(a => a?.stack) });
}; | 注意控制上报频率,避免过多网络请求 |
| 调试信息捕获与上报 | 拦截 warn/log 并上传至服务器 | 在生产环境收集调试信息,辅助问题定位 | console.log = function(...args) { if (window.DEBUG_MODE) { sendToServer('/debug-log', { level: 'log', args, time: Date.now() }); } originalLog.apply(console, args);
}; | 仅在调试模式启用,避免泄露敏感信息 |
| 安全审计:敏感信息检测 | 检测输出内容是否包含密码、token 等关键词 | 防止开发者误将隐私数据打印到控制台 | 见下方代码块 | 需定义完整的敏感关键词列表,持续更新 |
| 日志聚合与分类 | 根据方法类型打标签并结构化存储 | 统一管理前端日志,便于后续查询与分析 | 见下方代码块 | 使用缓冲区可提升性能,定期将 buffer 上报或导出 |
| 自动错误追踪 | 结合 error 事件与 console.error 捕获异常 | 提升前端异常捕获率,补充 try-catch 未覆盖场景 | window.addEventListener('error', e => { console.error('Global Error:', e.error);
}); | 注意跨域脚本错误信息受限(仅显示 “Script error.”) |
| 用户行为回放 | 记录所有 console 输出作为调试上下文 | 在用户反馈问题时还原其操作过程 | 见下方代码块 | 存储需控制大小,建议设置最大条数或自动清理机制 |
| 性能瓶颈预警 | 监控 console.timeEnd 输出的耗时数据 | 自动识别长时间运行的操作 | 见下方代码块 | 需解析 timeEnd 输出文本,不同浏览器格式可能略有差异 |
| 开发者教育与规范提醒 | 当使用废弃或危险方法时给出提示 | 提升团队代码质量,减少低级错误 | console.assert = function(condition, msg) { if (!condition) { console.warn([团队规范] 请检查断言失败: ${msg}); } originalAssert.apply(console, arguments);
}; | 可结合 ESLint 等工具,形成开发闭环 |
安全审计:敏感信息检测示例
console.log = function(...args) {
const leak = args.some(arg =>
/password|token|secret|authorization/i.test(String(arg))
);
if (leak) {
securityAlert("检测到潜在敏感信息泄露");
}
originalLog.apply(console, args);
};
日志聚合与分类示例
const levels = ['log', 'warn', 'error'];
levels.forEach(level => {
const orig = console[level];
console[level] = function(...args) {
logBuffer.push({
level,
timestamp: new Date(),
content: args,
page: location.pathname
});
orig.apply(console, args);
};
});
用户行为回放示例
const userLogs = [];
console.log = function(...args) {
userLogs.push({ type: 'log', args, time: performance.now() });
originalLog.apply(console, args);
};
// 用户提交问题时发送 userLogs
性能瓶颈预警示例
console.timeEnd = function(label) {
const start = performance.now();
const result = originalTimeEnd.call(console, label);
// 假设格式为 "label: 5000ms"
const ms = parseFloat(result.split(':')[1]);
if (ms > 3000) {
performanceAlert(`耗时过长: ${label} - ${ms}ms`);
}
};
跨浏览器兼容性与性能考量
| 方法名称 | 语法 | 用途说明 | 代码示例 | 注意事项 |
|---|
| console 存在性检测 | if (typeof console !== 'undefined') | 防止在无 console 对象的旧浏览器中报错 | if (typeof console === 'object' && console.log) { console.log("支持 console");
} else { console = { log: function(){}, warn: function(){} };
} | IE9 以下版本在无开发者工具时 console 为 undefined |
| 方法级兼容性检查 | if (console.time) { ... } | 确保调用的方法在当前浏览器中存在 | if (console.table) { console.table(data);
} else { console.log(data);
} | Safari 和旧版 Edge 可能不支持 table、groupCollapsed 等 |
| 安全调用封装 | try { console.method() } catch(e) {} | 防止因 console 方法不可用导致脚本中断 | try { console.debug("调试信息");
} catch(e) { // 忽略错误
} | 适用于生产环境,避免调试代码影响主流程 |
| 性能基准测试 | performance.now() 测量 hook 前后耗时 | 评估 hook 对页面性能的影响 | const start = performance.now();
console.log("测试");
const end = performance.now();
console.debug(Hook 耗时: ${end - start}ms); | 高频日志场景需特别关注单次调用延迟 |
| 减少字符串拼接 | 使用参数列表而非 + 拼接 | 降低 hook 中的额外开销 | // 推荐:
console.log("用户:", user.name);
// 避免:
console.log("用户:" + user.name + " 登录"); | 拼接操作在大量日志中会显著增加 CPU 占用 |
| 异步上报日志 | setTimeout 或 sendBeacon 发送数据 | 避免阻塞主线程,提升用户体验 | console.log = function(...args) { if (window.beaconSupported) { navigator.sendBeacon('/log', JSON.stringify(args)); } originalLog.apply(console, args);
}; | sendBeacon 在页面卸载时仍可发送数据,适合错误上报 |
| 批量处理与节流 | 使用缓冲区累积日志,定时批量处理 | 减少 I/O 操作和网络请求频率 | const buffer = [];
setInterval(() => { if (buffer.length > 0) { sendLogs(buffer); buffer.length = 0; }
}, 5000);
// hook 中只 push 到 buffer | 避免因频繁磁盘/网络写入导致性能下降 |
| 条件启用 Hook | 仅在开发环境或特定用户启用 | 生产环境关闭非必要 hook,减少运行时开销 | `if (location.hostname === ‘localhost’ | |
| 内存泄漏防范 | 避免在闭包中引用大型对象 | 防止 hook 函数长期持有对象引用导致无法回收 | // 错误:
console.log = function(data) { largeCache.push(data); // 引用大型数据
};
// 正确:限制缓存大小或使用 WeakMap | 特别注意日志缓冲区的大小控制,防止无限增长 |
解除 Hook 与恢复原始行为
| 方法名称 | 语法 | 用途说明 | 代码示例 | 注意事项 |
|---|
| 直接恢复原始方法 | console.log = originalLog; | 将被重写的方法重新指向保存的原始引用 | const originalLog = console.log;
console.log = function(...args) { console.debug("拦截:", ...args); originalLog.apply(console, args);
};
// 恢复:
console.log = originalLog; | 必须确保 originalLog 在 hook 前已正确保存,不可逆序操作 |
| 批量恢复方法 | 循环赋值还原多个 console 方法 | 一次性解除对 log、warn、error 等多个方法的 hook | const originals = { log: console.log, warn: console.warn, error: console.error
};
// ...执行 hook...
// 恢复:
Object.keys(originals).forEach(method => { console[method] = originals[method];
}); | 推荐用于通用 hook 框架,提升代码整洁性 |
| 使用 restore 函数封装 | restoreConsole() | 封装恢复逻辑,提供清晰的解绑接口 | let isHooked = false;
const originals = {};
function restoreConsole() { if (!isHooked) return; Object.keys(originals).forEach(method => { console[method] = originals[method]; }); isHooked = false;
} | 结合状态标记,防止重复恢复或在未 hook 时调用 |
| 防止恢复失败 | 检查目标方法是否仍为函数 | 确保恢复过程中不会因引用丢失导致异常 | function safeRestore(method, original) { if (typeof original === 'function') { console[method] = original; } else { console.warn(无法恢复 ${method}:原始引用无效); }
} | 增加健壮性,避免因意外覆盖导致恢复失败 |
| 解除 defineProperty 锁定 | delete console.log(不可行) | 注意:若使用 defineProperty({configurable: false}) 则无法解除 | Object.defineProperty(console, 'log', { writable: false, configurable: false // 一旦设为 false,无法删除或修改
}); | 一旦锁定不可逆,设计时需谨慎权衡安全与灵活性 |
| 暂时性解除与临时绕过 | 调用原始引用而不改变当前方法 | 在特定场景下跳过 hook 逻辑 | const originalError = console.error;
console.error = function(...args) { customHandler(args); originalError.apply(console, args);
};
// 临时绕过:
originalError("此条不经过 hook"); | 适用于调试 hook 本身或发送紧急日志 |
| 清理定时自检任务 | clearInterval(checkInterval) | 若使用了定时检测机制,恢复时应清除定时器 | let checkInterval = setInterval(checkHookIntegrity, 1000);
function restoreConsole() { clearInterval(checkInterval); // 恢复原始方法...
} | 避免内存泄漏和无谓的 CPU 消耗 |
| 恢复后的状态验证 | console.log.toString() === '[native code]' | 验证恢复是否成功 | restoreConsole();
if (console.log.toString().includes('[native code]')) { console.info("console.log 已成功恢复");
} else { console.error("恢复失败");
} | 辅助调试,确认最终状态符合预期 |