惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
人人都是产品经理
人人都是产品经理
IT之家
IT之家
T
The Blog of Author Tim Ferriss
V
V2EX
博客园 - 聂微东
The Cloudflare Blog
Blog — PlanetScale
Blog — PlanetScale
A
About on SuperTechFans
U
Unit 42
Vercel News
Vercel News
L
LangChain Blog
博客园 - 司徒正美
H
Help Net Security
Recent Announcements
Recent Announcements
Recorded Future
Recorded Future
V
Visual Studio Blog
Jina AI
Jina AI
Microsoft Azure Blog
Microsoft Azure Blog
GbyAI
GbyAI
Y
Y Combinator Blog
C
Check Point Blog
博客园 - 三生石上(FineUI控件)
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
J
Java Code Geeks
The Register - Security
The Register - Security
The GitHub Blog
The GitHub Blog
B
Blog RSS Feed
F
Fortinet All Blogs
B
Blog
G
Google Developers Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
云风的 BLOG
云风的 BLOG
爱范儿
爱范儿
MongoDB | Blog
MongoDB | Blog
F
Full Disclosure
有赞技术团队
有赞技术团队
罗磊的独立博客
博客园_首页
MyScale Blog
MyScale Blog
aimingoo的专栏
aimingoo的专栏
Google DeepMind News
Google DeepMind News
M
MIT News - Artificial intelligence
N
Netflix TechBlog - Medium
Engineering at Meta
Engineering at Meta
量子位
I
InfoQ
小众软件
小众软件
P
Proofpoint News Feed

AirCloud 日常

快速生成 AI 应用的框架对比:Gradio、Streamlit 和 Dash Node.js 二进制打包:pkg 原理解析 - AirCloud 日常 基于 K8s 容器化运行 web 端对端测试 关于 JupyterLab cell 输出保持问题的设计方案 - AirCloud 日常 读完 type-challenges,我总结了如下常用的内容 - AirCloud 日常 使用 Rust 开发 python 模块 基于 Container 架构的多框架前端应用 - AirCloud 日常 我们应该如何进行 Code Review ? - AirCloud 日常 Clean Code》读书笔记 - AirCloud 日常 使用 fst-json 自动生成更快的 json 序列化方法 《代码精进之路》与《代码整洁之道》-读书笔记 - AirCloud 日常 《重构》-读书笔记 - AirCloud 日常 《架构整洁之道》-读书笔记 - AirCloud 日常 React Conf 2021 内容概要 - AirCloud 日常 PC 开发技术选型:Electron 不是银弹 - AirCloud 日常 通过优化合成层优化性能 - AirCloud 日常 了解 StackOverFlow 上面最受欢迎的语言 Rust - AirCloud 日常 一些性能相关的 JavaScript 代码编写建议规范 - AirCloud 日常 如何让你的精力更多的用于提高技术深度 - AirCloud 日常
web 页面内存分析与生产环境禁用 console - AirCloud 日常
2021-09-04 · via AirCloud 日常

背景

我们在开发前端页面中,建议在生产环境中将所有的 console 禁用,并通过自定义的日志函数进行日志输出,即使无法禁用,也需要自定义文本过滤函数,严格控制 console 的输出。

但实际上,笔者经历的项目中很多都没有办法做到这一点,虽然我们知道,禁用 console 的主要原因除了信息泄漏的风险外,还有就是 console 打印的内容无法被内存回收。但仍然总是会有一些同学对禁用 console 的必要性表示质疑,在本篇文章中,本文通过两个实际遇到的比较严重的例子,来解释禁用 console 的必要性。

出于保密性考虑,例子本身已经脱敏,本文使用示例代码模拟原始场景。

页面内存

在具体例子讲解之前,我们需要先对页面内存有一个认知,在前端开发中,我们虽然开发的只是在 Chrome 等浏览器中浏览的页面,但是对页面的 cpu 和 内存占用也需要时刻保持关注。

cpu 和 内存一般是针对进程级别,chrome 的进程模型比较复杂,一般情况下,我们可以认为同域的页面有比较大的概率进行进程复用。

Chrome 提供了一些手段,让我们可以监控页面的 cpu 和内存,例如:

Performance Monitor 可以让我们直观地监测页面的 cpu、js heap 的分配情况等:

chrome_monitor

Chrome 自身提供了一个任务管理器(More Tools -> Task Manager),可以让我们关注各个页面的性能情况:

chrome_task_manager

除了实时监控以外,Chrome DevTools 的 Memory 等 tab 也可以让我们对内存占用进行取样分析,以及内存泄漏分析:

  • 一般来说,我们可以通过对两次 heap snapshot,然后搜索关键变量的数目与引用关系是否符合预期,来证明是否存在内存泄漏。
  • 除此之外,我们使用 WeakMap 来跟踪我们的实例,也可以辅助进行一定的内存泄漏分析。

使用 console.log 打印 dom 元素造成死循环 OOM

之前笔者负责的一个页面,在某个版本出现了一个问题:打开页面后不久,在什么操作也没有做的情况下直接卡死无响应。

一般来说,js 导致网页无响应的可能性并不多,我们首先怀疑是因为死循环导致的。

不过我们通过对比上次和这一次的代码,发现变动极小(实际上,我们一开始都忽略了 console.log),我们通过在 Chrome 的 devTools 里面打断点,最终定位发现是卡死在第三方库 sentry 的 console.log 中。

最终我们定位出真正的原因:其中一处 try catch 在 catch 到错误之后,会 console.log 打印包括 dom 在内的一些内容,而我们使用的 console.log 被 sentry 进行了覆盖,它的覆盖方法大致如下(这个确实有点坑,以至于我们直接查看 console.log 仍然是 [native code], 不过最新版本的 Chrome 这个代码已经不能完全 work):

1
2
3
4
5
6
7
8
9
10
11
let __native_console = console.log;
console.log = function() {

__native_console(...arguments);
}
console.log.prototype.__native_console = __native_console;
console.log.prototype.toString = function() {
if (this.__native_console) return this.__native_console.toString();
return this.toString();
}

这里之所以死循环,是因为 React 中 FiberNode 是 Dom 的其中一个属性,console.log 递归遍历到了 FiberNode,其本质是一个双向链表,最终造成无限递归死循环

我们可以比较方便的随便找个 React 项目验证这一点:

React Fiber

detached dom 过多导致页面内存持续上涨

另外笔者接触到的一个比较严重的问题,是之前某项目的一个页面,随着使用时间增加,页面的内存使用量快速持续增加,最终导致卡顿和崩溃。

这个问题的定位过程也比较艰辛,最终发现其中的一个主要原因是 console.log 打印了 dom 节点,导致 detached dom 持续增多并且无法被回收,最终导致严重问题

关于 detached dom 的问题我们可以使用通过压缩合成层优化性能 这里的 demo,简单修改:

将原本需要挂载到 dom 的节点直接进行打印:

1
2
3
4
5
6
7
for(let i = 0; i < totalListCount; i += 1) {
let fragment = document.createElement("div");
fragment.classList.add("li");
fragment.innerHTML = `<p>this is the ${i} element</p>`;
console.log(fragment);

}

我们很容易看到这样就产生了 500 个 detach 节点,并且在页面的生命周期内,无法进行释放:

detach console

总结

实际上,在生产环境使用 console.log 造成的问题远不止上面的两例,而且这类问题通常排查起来都会比较艰难,因此,建议大家落实在生产环境禁用 console。